Skip to content
Snippets Groups Projects
Commit 2ca377d8 authored by Hordur Freyr Yngvason's avatar Hordur Freyr Yngvason :baby:
Browse files

Remove cluster applications frontend code

The code being removed has been unused since the removal of the
applications tab in
!63348

Part of &4280
parent c044eeae
No related branches found
No related tags found
1 merge request!64111Remove cluster applications frontend code
Showing
with 6 additions and 3008 deletions
......@@ -3,16 +3,12 @@ import Visibility from 'visibilityjs';
import Vue from 'vue';
import AccessorUtilities from '~/lib/utils/accessor';
import initProjectSelectDropdown from '~/project_select';
import initServerlessSurveyBanner from '~/serverless/survey_banner';
import createFlash from '../flash';
import Poll from '../lib/utils/poll';
import { s__, sprintf } from '../locale';
import { s__ } from '../locale';
import PersistentUserCallout from '../persistent_user_callout';
import initSettingsPanels from '../settings_panels';
import Applications from './components/applications.vue';
import RemoveClusterConfirmation from './components/remove_cluster_confirmation.vue';
import { APPLICATION_STATUS, CROSSPLANE, KNATIVE } from './constants';
import eventHub from './event_hub';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
......@@ -20,46 +16,20 @@ const Environments = () => import('ee_component/clusters/components/environments
Vue.use(GlToast);
/**
* 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,
installCertManagerPath,
installRunnerPath,
installJupyterPath,
installKnativePath,
updateKnativePath,
installElasticStackPath,
installCrossplanePath,
installPrometheusPath,
managePrometheusPath,
clusterEnvironmentsPath,
hasRbac,
providerType,
preInstalledKnative,
clusterType,
clusterStatus,
clusterStatusReason,
helpPath,
helmHelpPath,
ingressHelpPath,
ingressDnsHelpPath,
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
cloudRunHelpPath,
clusterId,
ciliumHelpPath,
} = document.querySelector('.js-edit-cluster-form').dataset;
this.clusterId = clusterId;
......@@ -69,38 +39,19 @@ export default class Clusters {
this.store = new ClustersStore();
this.store.setHelpPaths({
helpPath,
helmHelpPath,
ingressHelpPath,
ingressDnsHelpPath,
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
cloudRunHelpPath,
ciliumHelpPath,
});
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.store.updateProviderType(providerType);
this.store.updatePreInstalledKnative(preInstalledKnative);
this.store.updateRbac(hasRbac);
this.service = new ClustersService({
endpoint: statusPath,
installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath,
installCertManagerEndpoint: installCertManagerPath,
installCrossplaneEndpoint: installCrossplanePath,
installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath,
updateKnativeEndpoint: updateKnativePath,
installElasticStackEndpoint: installElasticStackPath,
clusterEnvironmentsEndpoint: clusterEnvironmentsPath,
});
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');
......@@ -109,14 +60,12 @@ export default class Clusters {
'.js-cluster-authentication-failure',
);
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.tokenField = document.querySelector('.js-cluster-token');
initProjectSelectDropdown();
Clusters.initDismissableCallout();
initSettingsPanels();
this.initApplications(clusterType);
this.initEnvironments();
if (clusterEnvironmentsPath && this.environments) {
......@@ -143,38 +92,6 @@ export default class Clusters {
this.initRemoveClusterActions();
}
initApplications(type) {
const { store } = this;
const el = document.querySelector('#js-cluster-applications');
this.applications = new Vue({
el,
data() {
return {
state: store.state,
};
},
render(createElement) {
return createElement(Applications, {
props: {
type,
applications: this.state.applications,
helpPath: this.state.helpPath,
helmHelpPath: this.state.helmHelpPath,
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
ingressDnsHelpPath: this.state.ingressDnsHelpPath,
cloudRunHelpPath: this.state.cloudRunHelpPath,
providerType: this.state.providerType,
preInstalledKnative: this.state.preInstalledKnative,
rbac: this.state.rbac,
ciliumHelpPath: this.state.ciliumHelpPath,
},
});
},
});
}
initEnvironments() {
const { store } = this;
const el = document.querySelector('#js-cluster-environments');
......@@ -242,30 +159,11 @@ export default class Clusters {
}
addListeners() {
eventHub.$on('installApplication', this.installApplication);
eventHub.$on('updateApplication', (data) => this.updateApplication(data));
eventHub.$on('saveKnativeDomain', (data) => this.saveKnativeDomain(data));
eventHub.$on('setKnativeDomain', (data) => this.setKnativeDomain(data));
eventHub.$on('uninstallApplication', (data) => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', (data) => this.setCrossplaneProviderStack(data));
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
}
removeListeners() {
eventHub.$off('installApplication', this.installApplication);
eventHub.$off('updateApplication', this.updateApplication);
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('saveKnativeDomain');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setKnativeDomain');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('setCrossplaneProviderStack');
// eslint-disable-next-line @gitlab/no-global-event-off
eventHub.$off('uninstallApplication');
}
initPolling(method, successCallback, errorCallback) {
this.poll = new Poll({
resource: this.service,
......@@ -305,16 +203,10 @@ export default class Clusters {
handleClusterStatusSuccess(data) {
const prevStatus = this.store.state.status;
const prevApplicationMap = { ...this.store.state.applications };
this.store.updateStateFromServer(data.data);
this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
if (this.store.state.applications[KNATIVE]?.status === APPLICATION_STATUS.INSTALLED) {
initServerlessSurveyBanner();
}
}
hideAll() {
......@@ -325,31 +217,6 @@ export default class Clusters {
this.authenticationFailureContainer.classList.add('hidden');
}
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
const appTitles = Object.keys(newApplicationMap)
.filter(
(appId) =>
newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== null,
)
.map((appId) => newApplicationMap[appId].title);
if (appTitles.length > 0) {
const text = sprintf(
s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
{
appList: appTitles.join(', '),
},
);
createFlash({
message: text,
type: 'notice',
parent: this.successApplicationContainer,
});
}
}
setBannerDismissedState(status, isDismissed) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
window.localStorage.setItem(this.clusterBannerDismissedKey, `${status}_${isDismissed}`);
......@@ -422,91 +289,9 @@ export default class Clusters {
}
}
installApplication({ id: appId, params }) {
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));
}
static validateInstallation(appId, params) {
return new Promise((resolve, reject) => {
if (appId === CROSSPLANE && !params.stack) {
reject(s__('ClusterIntegration|Select a stack to install Crossplane.'));
return;
}
if (appId === KNATIVE && !params.hostname && !params.pages_domain_id) {
reject(s__('ClusterIntegration|You must specify a domain before you can install Knative.'));
return;
}
resolve();
});
}
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'),
);
});
}
updateApplication({ id: appId, params }) {
this.store.updateApplication(appId);
this.service.installApplication(appId, params).catch(() => {
this.store.notifyUpdateFailure(appId);
});
}
saveKnativeDomain(data) {
const appId = data.id;
this.store.updateApplication(appId);
this.service.updateApplication(appId, data.params).catch(() => {
this.store.notifyUpdateFailure(appId);
});
}
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);
this.store.updateAppProperty(appId, 'validationError', null);
}
setCrossplaneProviderStack(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'stack', data.stack.code);
this.store.updateAppProperty(appId, 'validationError', null);
}
destroy() {
this.destroyed = true;
this.removeListeners();
if (this.poll) {
this.poll.stop();
}
......@@ -514,7 +299,5 @@ export default class Clusters {
if (this.environments) {
this.environments.$destroy();
}
this.applications.$destroy();
}
}
<script>
import { GlLink, GlModalDirective, GlSprintf, GlButton, GlAlert } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import identicon from '../../vue_shared/components/identicon.vue';
import { APPLICATION_STATUS, ELASTIC_STACK } from '../constants';
import eventHub from '../event_hub';
import UninstallApplicationButton from './uninstall_application_button.vue';
import UninstallApplicationConfirmationModal from './uninstall_application_confirmation_modal.vue';
import UpdateApplicationConfirmationModal from './update_application_confirmation_modal.vue';
export default {
components: {
GlButton,
identicon,
GlLink,
GlAlert,
GlSprintf,
UninstallApplicationButton,
UninstallApplicationConfirmationModal,
UpdateApplicationConfirmationModal,
},
directives: {
GlModalDirective,
},
props: {
id: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
titleLink: {
type: String,
required: false,
default: '',
},
manageLink: {
type: String,
required: false,
default: '',
},
logoUrl: {
type: String,
required: false,
default: '',
},
disabled: {
type: Boolean,
required: false,
default: false,
},
installable: {
type: Boolean,
required: false,
default: true,
},
uninstallable: {
type: Boolean,
required: false,
default: false,
},
status: {
type: String,
required: false,
default: '',
},
statusReason: {
type: String,
required: false,
default: '',
},
requestReason: {
type: String,
required: false,
default: '',
},
installed: {
type: Boolean,
required: false,
default: false,
},
installFailed: {
type: Boolean,
required: false,
default: false,
},
version: {
type: String,
required: false,
default: '',
},
chartRepo: {
type: String,
required: false,
default: '',
},
updateAvailable: {
type: Boolean,
required: false,
},
updateable: {
type: Boolean,
default: true,
required: false,
},
updateSuccessful: {
type: Boolean,
required: false,
default: false,
},
updateFailed: {
type: Boolean,
required: false,
default: false,
},
uninstallFailed: {
type: Boolean,
required: false,
default: false,
},
uninstallSuccessful: {
type: Boolean,
required: false,
default: false,
},
installApplicationRequestParams: {
type: Object,
required: false,
default: () => ({}),
},
},
computed: {
isUnknownStatus() {
return !this.isKnownStatus && this.status !== null;
},
isKnownStatus() {
return Object.values(APPLICATION_STATUS).includes(this.status);
},
isInstalling() {
return this.status === APPLICATION_STATUS.INSTALLING;
},
isExternallyInstalled() {
return this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED;
},
canInstall() {
return (
this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
this.status === APPLICATION_STATUS.INSTALLABLE ||
this.status === APPLICATION_STATUS.UNINSTALLED ||
this.isUnknownStatus
);
},
hasLogo() {
return Boolean(this.logoUrl);
},
identiconId() {
// generate a deterministic integer id for the identicon background
return this.id.charCodeAt(0);
},
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
displayUninstallButton() {
return this.installed && this.uninstallable;
},
displayInstallButton() {
return !this.installed || !this.uninstallable;
},
installButtonLoading() {
return !this.status || this.isInstalling;
},
installButtonDisabled() {
// Applications installed through the management project can
// only be installed through the CI pipeline. Installation should
// be disable in all states.
if (!this.installable) return true;
// Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
if (this.isInstalling) return true;
if (!this.isKnownStatus) return false;
return (
this.status !== APPLICATION_STATUS.INSTALLABLE && this.status !== APPLICATION_STATUS.ERROR
);
},
installButtonLabel() {
let label;
if (this.canInstall) {
label = __('Install');
} else if (this.isInstalling) {
label = __('Installing');
} else if (this.installed) {
label = __('Installed');
} else if (this.isExternallyInstalled) {
label = __('Externally installed');
}
return label;
},
buttonGridCellClass() {
return this.showManageButton || this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED
? 'section-25'
: 'section-15';
},
showManageButton() {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
manageButtonLabel() {
return __('Manage');
},
hasError() {
return this.installFailed || this.uninstallFailed;
},
generalErrorDescription() {
let errorDescription;
if (this.installFailed) {
errorDescription = s__('ClusterIntegration|Something went wrong while installing %{title}');
} else if (this.uninstallFailed) {
errorDescription = s__(
'ClusterIntegration|Something went wrong while uninstalling %{title}',
);
}
return sprintf(errorDescription, { title: this.title });
},
updateFailureDescription() {
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
},
updateSuccessDescription() {
return sprintf(s__('ClusterIntegration|%{title} updated successfully.'), {
title: this.title,
});
},
updateButtonLabel() {
let label;
if (this.updateAvailable && !this.updateFailed && !this.isUpdating) {
label = __('Update');
} else if (this.isUpdating) {
label = __('Updating');
} else if (this.updateFailed) {
label = __('Retry update');
}
return label;
},
updatingNeedsConfirmation() {
if (this.version) {
const majorVersion = parseInt(this.version.split('.')[0], 10);
if (!Number.isNaN(majorVersion)) {
return this.id === ELASTIC_STACK && majorVersion < 3;
}
}
return false;
},
isUpdating() {
// Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
return this.status === APPLICATION_STATUS.UPDATING;
},
shouldShowUpdateDetails() {
// This method only returns true when;
// Update was successful OR Update failed
// AND new update is unavailable AND version information is present.
return (this.updateSuccessful || this.updateFailed) && !this.updateAvailable && this.version;
},
uninstallSuccessDescription() {
return sprintf(s__('ClusterIntegration|%{title} uninstalled successfully.'), {
title: this.title,
});
},
updateModalId() {
return `update-${this.id}`;
},
uninstallModalId() {
return `uninstall-${this.id}`;
},
},
watch: {
updateSuccessful(updateSuccessful) {
if (updateSuccessful) {
this.$toast.show(this.updateSuccessDescription);
}
},
uninstallSuccessful(uninstallSuccessful) {
if (uninstallSuccessful) {
this.$toast.show(this.uninstallSuccessDescription);
}
},
},
methods: {
installClicked() {
if (this.disabled || this.installButtonDisabled) return;
eventHub.$emit('installApplication', {
id: this.id,
params: this.installApplicationRequestParams,
});
},
updateConfirmed() {
if (this.isUpdating) return;
eventHub.$emit('updateApplication', {
id: this.id,
params: this.installApplicationRequestParams,
});
},
uninstallConfirmed() {
eventHub.$emit('uninstallApplication', {
id: this.id,
});
},
},
};
</script>
<template>
<div
:class="[
rowJsClass,
installed && 'cluster-application-installed',
disabled && 'cluster-application-disabled',
]"
class="cluster-application-row gl-responsive-table-row gl-responsive-table-row-col-span"
:data-qa-selector="id"
>
<div class="gl-responsive-table-row-layout" role="row">
<div class="table-section gl-mr-3 section-align-top" role="gridcell">
<img
v-if="hasLogo"
:src="logoUrl"
:alt="`${title} logo`"
class="cluster-application-logo avatar s40"
/>
<identicon v-else :entity-id="identiconId" :entity-name="title" size-class="s40" />
</div>
<div class="table-section cluster-application-description section-wrap" role="gridcell">
<strong>
<a
v-if="titleLink"
:href="titleLink"
target="_blank"
rel="noopener noreferrer"
class="js-cluster-application-title"
>{{ title }}</a
>
<span v-else class="js-cluster-application-title">{{ title }}</span>
</strong>
<slot name="installed-via"></slot>
<div>
<slot name="description"></slot>
</div>
<div v-if="hasError" class="cluster-application-error text-danger gl-mt-3">
<p class="js-cluster-application-general-error-message gl-mb-0">
{{ generalErrorDescription }}
</p>
<ul v-if="statusReason || requestReason">
<li v-if="statusReason" class="js-cluster-application-status-error-message">
{{ statusReason }}
</li>
<li v-if="requestReason" class="js-cluster-application-request-error-message">
{{ requestReason }}
</li>
</ul>
</div>
<div v-if="updateable">
<div
v-if="shouldShowUpdateDetails"
class="form-text text-muted label p-0 js-cluster-application-update-details"
>
<template v-if="updateFailed">{{ __('Update failed') }}</template>
<template v-else-if="isUpdating">{{ __('Updating') }}</template>
<template v-else>
<gl-sprintf :message="__('Updated to %{linkStart}chart v%{linkEnd}')">
<template #link="{ content }">
<gl-link
:href="chartRepo"
target="_blank"
class="js-cluster-application-update-version"
>{{ content }}{{ version }}</gl-link
>
</template>
</gl-sprintf>
</template>
</div>
<gl-alert
v-if="updateFailed && !isUpdating"
variant="danger"
:dismissible="false"
class="gl-mt-3 gl-mb-0 js-cluster-application-update-details"
>
{{ updateFailureDescription }}
</gl-alert>
<template v-if="updateAvailable || updateFailed || isUpdating">
<template v-if="updatingNeedsConfirmation">
<gl-button
v-gl-modal-directive="updateModalId"
class="js-cluster-application-update-button mt-2"
variant="info"
category="primary"
:loading="isUpdating"
:disabled="isUpdating"
data-qa-selector="update_button_with_confirmation"
:data-qa-application="id"
>
{{ updateButtonLabel }}
</gl-button>
<update-application-confirmation-modal
:application="id"
:application-title="title"
@confirm="updateConfirmed()"
/>
</template>
<gl-button
v-else
class="js-cluster-application-update-button mt-2"
variant="info"
category="primary"
:loading="isUpdating"
:disabled="isUpdating"
data-qa-selector="update_button"
:data-qa-application="id"
@click="updateConfirmed"
>
{{ updateButtonLabel }}
</gl-button>
</template>
</div>
</div>
<div
:class="[buttonGridCellClass, 'table-section', 'table-button-footer', 'section-align-top']"
role="gridcell"
>
<div v-if="showManageButton" class="btn-group table-action-buttons">
<a :href="manageLink" :class="{ disabled: disabled }" class="btn">{{
manageButtonLabel
}}</a>
</div>
<div class="btn-group table-action-buttons">
<gl-button
v-if="displayInstallButton"
:loading="installButtonLoading"
:disabled="disabled || installButtonDisabled"
class="js-cluster-application-install-button"
variant="default"
data-qa-selector="install_button"
:data-qa-application="id"
@click="installClicked"
>
{{ installButtonLabel }}
</gl-button>
<uninstall-application-button
v-if="displayUninstallButton"
v-gl-modal-directive="uninstallModalId"
:status="status"
data-qa-selector="uninstall_button"
:data-qa-application="id"
class="js-cluster-application-uninstall-button"
/>
<uninstall-application-confirmation-modal
:application="id"
:application-title="title"
@confirm="uninstallConfirmed()"
/>
</div>
</div>
</div>
</div>
</template>
This diff is collapsed.
<script>
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { s__ } from '../../locale';
export default {
name: 'CrossplaneProviderStack',
components: {
GlDropdown,
GlDropdownItem,
GlIcon,
},
props: {
stacks: {
type: Array,
required: false,
default: () => [
{
name: s__('Google Cloud Platform'),
code: 'gcp',
},
{
name: s__('Amazon Web Services'),
code: 'aws',
},
{
name: s__('Microsoft Azure'),
code: 'azure',
},
{
name: s__('Rook'),
code: 'rook',
},
],
},
crossplane: {
type: Object,
required: true,
},
},
computed: {
dropdownText() {
const result = this.stacks.reduce((map, obj) => {
// eslint-disable-next-line no-param-reassign
map[obj.code] = obj.name;
return map;
}, {});
const { stack } = this.crossplane;
if (stack !== '') {
return result[stack];
}
return s__('Select Stack');
},
validationError() {
return this.crossplane.validationError;
},
},
methods: {
selectStack(stack) {
this.$emit('set', stack);
},
},
};
</script>
<template>
<div>
<label>
{{ s__('ClusterIntegration|Enabled stack') }}
</label>
<gl-dropdown
:disabled="crossplane.installed"
:text="dropdownText"
toggle-class="dropdown-menu-toggle gl-field-error-outline"
class="w-100"
:class="{ 'gl-show-field-errors': validationError }"
>
<gl-dropdown-item v-for="stack in stacks" :key="stack.code" @click="selectStack(stack)">
<span class="ml-1">{{ stack.name }}</span>
</gl-dropdown-item>
</gl-dropdown>
<span v-if="validationError" class="gl-field-error">{{ validationError }}</span>
<p class="form-text text-muted">
{{ s__(`You must select a stack for configuring your cloud provider. Learn more about`) }}
<a
href="https://crossplane.io/docs/master/stacks-guide.html"
target="_blank"
rel="noopener noreferrer"
>{{ __('Crossplane') }}
<gl-icon name="external-link" class="vertical-align-middle" />
</a>
</p>
</div>
</template>
<script>
import {
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlSprintf,
GlButton,
GlAlert,
} from '@gitlab/ui';
import { APPLICATION_STATUS } from '~/clusters/constants';
import { __, s__ } from '~/locale';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
export default {
components: {
GlButton,
ClipboardButton,
GlLoadingIcon,
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByType,
GlSprintf,
GlAlert,
},
props: {
knative: {
type: Object,
required: true,
},
ingressDnsHelpPath: {
type: String,
default: '',
required: false,
},
},
data() {
return {
searchQuery: '',
};
},
computed: {
saveButtonDisabled() {
return [UNINSTALLING, UPDATING].includes(this.knative.status);
},
saving() {
return [UPDATING].includes(this.knative.status);
},
saveButtonLabel() {
return this.saving ? __('Saving') : __('Save changes');
},
knativeInstalled() {
return this.knative.installed;
},
knativeExternalEndpoint() {
return this.knative.externalIp || this.knative.externalHostname;
},
knativeUpdateSuccessful() {
return this.knative.updateSuccessful;
},
knativeHostname: {
get() {
return this.knative.hostname;
},
set(hostname) {
this.selectCustomDomain(hostname);
},
},
domainDropdownText() {
return this.knativeHostname || s__('ClusterIntegration|Select existing domain or use new');
},
availableDomains() {
return this.knative.availableDomains || [];
},
filteredDomains() {
const query = this.searchQuery.toLowerCase();
return this.availableDomains.filter(({ domain }) => domain.toLowerCase().includes(query));
},
showDomainsDropdown() {
return this.availableDomains.length > 0;
},
validationError() {
return this.knative.validationError;
},
},
watch: {
knativeUpdateSuccessful(updateSuccessful) {
if (updateSuccessful) {
this.$toast.show(s__('ClusterIntegration|Knative domain name was updated successfully.'));
}
},
},
methods: {
selectDomain({ id, domain }) {
this.$emit('set', { domain, domainId: id });
},
selectCustomDomain(domain) {
this.$emit('set', { domain, domainId: null });
},
},
};
</script>
<template>
<div class="row">
<gl-alert
v-if="knative.updateFailed"
class="gl-mb-5 col-12 js-cluster-knative-domain-name-failure-message"
variant="danger"
>
{{ s__('ClusterIntegration|Something went wrong while updating Knative domain name.') }}
</gl-alert>
<div
:class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }"
class="form-group col-sm-12 mb-0"
>
<label for="knative-domainname">
<strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
</label>
<gl-dropdown
v-if="showDomainsDropdown"
:text="domainDropdownText"
toggle-class="dropdown-menu-toggle"
class="w-100 mb-2"
>
<gl-search-box-by-type
v-model.trim="searchQuery"
:placeholder="s__('ClusterIntegration|Search domains')"
/>
<gl-dropdown-item
v-for="domain in filteredDomains"
:key="domain.id"
@click="selectDomain(domain)"
>
<span class="ml-1">{{ domain.domain }}</span>
</gl-dropdown-item>
<template v-if="searchQuery">
<gl-dropdown-divider />
<gl-dropdown-item key="custom-domain" @click="selectCustomDomain(searchQuery)">
<span class="ml-1">
<gl-sprintf :message="s__('ClusterIntegration|Use %{query}')">
<template #query>
<code>{{ searchQuery }}</code>
</template>
</gl-sprintf>
</span>
</gl-dropdown-item>
</template>
</gl-dropdown>
<input
v-else
id="knative-domainname"
v-model="knativeHostname"
type="text"
class="form-control js-knative-domainname"
/>
<span v-if="validationError" class="gl-field-error">{{ validationError }}</span>
</div>
<template v-if="knativeInstalled">
<div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
<label for="knative-endpoint">
<strong>{{ s__('ClusterIntegration|Knative Endpoint:') }}</strong>
</label>
<div v-if="knativeExternalEndpoint" class="input-group">
<input
id="knative-endpoint"
:value="knativeExternalEndpoint"
type="text"
class="form-control js-knative-endpoint"
readonly
/>
<span class="input-group-append">
<clipboard-button
:text="knativeExternalEndpoint"
:title="s__('ClusterIntegration|Copy Knative Endpoint')"
class="input-group-text js-knative-endpoint-clipboard-btn"
/>
</span>
</div>
<div v-else class="input-group">
<input type="text" class="form-control js-endpoint" readonly />
<gl-loading-icon
class="position-absolute align-self-center ml-2 js-knative-ip-loading-icon"
/>
</div>
</div>
<p class="form-text text-muted col-12">
{{
s__(
`ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint.`,
)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
__('More information')
}}</a>
</p>
<p
v-if="!knativeExternalEndpoint"
class="settings-message js-no-knative-endpoint-message mt-2 mr-3 mb-0 ml-3"
>
{{
s__(`ClusterIntegration|The endpoint is in
the process of being assigned. Please check your Kubernetes
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}}
</p>
<gl-button
class="js-knative-save-domain-button gl-mt-5 gl-ml-5"
variant="success"
category="primary"
:loading="saving"
:disabled="saveButtonDisabled"
@click="$emit('save')"
>
{{ saveButtonLabel }}
</gl-button>
</template>
</div>
</template>
<script>
import { GlButton } from '@gitlab/ui';
import { APPLICATION_STATUS } from '~/clusters/constants';
import { __ } from '~/locale';
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
export default {
components: {
GlButton,
},
props: {
status: {
type: String,
required: true,
},
},
computed: {
disabled() {
return [UNINSTALLING, UPDATING].includes(this.status);
},
loading() {
return this.status === UNINSTALLING;
},
label() {
return this.loading ? __('Uninstalling') : __('Uninstall');
},
},
};
</script>
<template>
<gl-button :disabled="disabled" variant="default" :loading="loading">
{{ label }}
</gl-button>
</template>
<script>
import { GlModal, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click';
import { sprintf, s__ } from '~/locale';
import {
HELM,
INGRESS,
CERT_MANAGER,
PROMETHEUS,
RUNNER,
KNATIVE,
JUPYTER,
ELASTIC_STACK,
} from '../constants';
const CUSTOM_APP_WARNING_TEXT = {
[HELM]: sprintf(
s__(
'ClusterIntegration|The associated Tiller pod will be deleted and cannot be restored. Your other applications will remain unaffected.',
),
{
gitlabManagedAppsNamespace: '<code>gitlab-managed-apps</code>',
},
false,
),
[INGRESS]: s__(
'ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored.',
),
[CERT_MANAGER]: s__(
'ClusterIntegration|The associated private key will be deleted and cannot be restored.',
),
[PROMETHEUS]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
[RUNNER]: s__('ClusterIntegration|Any running pipelines will be canceled.'),
[KNATIVE]: s__(
'ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications.',
),
[JUPYTER]: s__(
'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
),
[ELASTIC_STACK]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
};
export default {
components: {
GlModal,
},
directives: {
SafeHtml,
},
mixins: [trackUninstallButtonClickMixin],
props: {
application: {
type: String,
required: true,
},
applicationTitle: {
type: String,
required: true,
},
},
computed: {
title() {
return sprintf(s__('ClusterIntegration|Uninstall %{appTitle}'), {
appTitle: this.applicationTitle,
});
},
warningText() {
return sprintf(
s__('ClusterIntegration|You are about to uninstall %{appTitle} from your cluster.'),
{
appTitle: this.applicationTitle,
},
);
},
customAppWarningText() {
return CUSTOM_APP_WARNING_TEXT[this.application];
},
modalId() {
return `uninstall-${this.application}`;
},
},
methods: {
confirmUninstall() {
this.trackUninstallButtonClick(this.application);
this.$emit('confirm');
},
},
};
</script>
<template>
<gl-modal
ok-variant="danger"
cancel-variant="light"
:ok-title="title"
:modal-id="modalId"
:title="title"
@ok="confirmUninstall()"
>
{{ warningText }} <span v-safe-html="customAppWarningText"></span>
</gl-modal>
</template>
<script>
/* eslint-disable vue/no-v-html */
import { GlModal } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { ELASTIC_STACK } from '../constants';
const CUSTOM_APP_WARNING_TEXT = {
[ELASTIC_STACK]: s__(
'ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days.',
),
};
export default {
components: {
GlModal,
},
props: {
application: {
type: String,
required: true,
},
applicationTitle: {
type: String,
required: true,
},
},
computed: {
title() {
return sprintf(s__('ClusterIntegration|Update %{appTitle}'), {
appTitle: this.applicationTitle,
});
},
warningText() {
return sprintf(
s__('ClusterIntegration|You are about to update %{appTitle} on your cluster.'),
{
appTitle: this.applicationTitle,
},
);
},
customAppWarningText() {
return CUSTOM_APP_WARNING_TEXT[this.application];
},
modalId() {
return `update-${this.application}`;
},
},
methods: {
confirmUpdate() {
this.$emit('confirm');
},
},
};
</script>
<template>
<gl-modal
ok-variant="danger"
cancel-variant="light"
:ok-title="title"
:modal-id="modalId"
:title="title"
@ok="confirmUpdate()"
>
{{ warningText }} <span v-html="customAppWarningText"></span>
</gl-modal>
</template>
......@@ -10,64 +10,7 @@ export const PROVIDER_TYPE = {
GCP: 'gcp',
};
// These need to match what is returned from the server
export const APPLICATION_STATUS = {
NO_STATUS: null,
NOT_INSTALLABLE: 'not_installable',
INSTALLABLE: 'installable',
SCHEDULED: 'scheduled',
INSTALLING: 'installing',
INSTALLED: 'installed',
UPDATING: 'updating',
UPDATED: 'updated',
UPDATE_ERRORED: 'update_errored',
UNINSTALLING: 'uninstalling',
UNINSTALL_ERRORED: 'uninstall_errored',
ERROR: 'errored',
PRE_INSTALLED: 'pre_installed',
UNINSTALLED: 'uninstalled',
EXTERNALLY_INSTALLED: 'externally_installed',
};
/*
* The application cannot be in any of the following states without
* not being installed.
*/
export const APPLICATION_INSTALLED_STATUSES = [
APPLICATION_STATUS.INSTALLED,
APPLICATION_STATUS.UPDATING,
APPLICATION_STATUS.UNINSTALLING,
APPLICATION_STATUS.PRE_INSTALLED,
];
// These are only used client-side
export const UPDATE_EVENT = 'update';
export const INSTALL_EVENT = 'install';
export const UNINSTALL_EVENT = 'uninstall';
export const HELM = 'helm';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
export const CROSSPLANE = 'crossplane';
export const PROMETHEUS = 'prometheus';
export const ELASTIC_STACK = 'elastic_stack';
export const APPLICATIONS = [
HELM,
INGRESS,
JUPYTER,
KNATIVE,
RUNNER,
CERT_MANAGER,
PROMETHEUS,
ELASTIC_STACK,
];
export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
export const LOGGING_MODE = 'logging';
export const BLOCKING_MODE = 'blocking';
import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT, UNINSTALL_EVENT } from '../constants';
const {
NO_STATUS,
SCHEDULED,
NOT_INSTALLABLE,
INSTALLABLE,
INSTALLING,
INSTALLED,
ERROR,
UPDATING,
UPDATED,
UPDATE_ERRORED,
UNINSTALLING,
UNINSTALL_ERRORED,
PRE_INSTALLED,
UNINSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS;
const applicationStateMachine = {
/* When the application initially loads, it will have `NO_STATUS`
* It will transition from `NO_STATUS` once the async backend call is completed
*/
[NO_STATUS]: {
on: {
[SCHEDULED]: {
target: INSTALLING,
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
},
[INSTALLABLE]: {
target: INSTALLABLE,
},
[INSTALLING]: {
target: INSTALLING,
},
[INSTALLED]: {
target: INSTALLED,
},
[ERROR]: {
target: INSTALLABLE,
effects: {
installFailed: true,
},
},
[UPDATING]: {
target: UPDATING,
},
[UPDATED]: {
target: INSTALLED,
},
[UPDATE_ERRORED]: {
target: INSTALLED,
effects: {
updateFailed: true,
},
},
[UNINSTALLING]: {
target: UNINSTALLING,
},
[UNINSTALL_ERRORED]: {
target: INSTALLED,
effects: {
uninstallFailed: true,
},
},
[PRE_INSTALLED]: {
target: PRE_INSTALLED,
},
[UNINSTALLED]: {
target: UNINSTALLED,
},
[EXTERNALLY_INSTALLED]: {
target: EXTERNALLY_INSTALLED,
},
},
},
[NOT_INSTALLABLE]: {
on: {
[INSTALLABLE]: {
target: INSTALLABLE,
},
},
},
[INSTALLABLE]: {
on: {
[INSTALL_EVENT]: {
target: INSTALLING,
effects: {
installFailed: false,
},
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
},
[INSTALLED]: {
target: INSTALLED,
effects: {
installFailed: false,
},
},
[UNINSTALLED]: {
target: UNINSTALLED,
effects: {
installFailed: false,
},
},
},
},
[INSTALLING]: {
on: {
[INSTALLED]: {
target: INSTALLED,
},
[ERROR]: {
target: INSTALLABLE,
effects: {
installFailed: true,
},
},
},
},
[INSTALLED]: {
on: {
[UPDATE_EVENT]: {
target: UPDATING,
effects: {
updateFailed: false,
updateSuccessful: false,
},
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
},
[UNINSTALL_EVENT]: {
target: UNINSTALLING,
effects: {
uninstallFailed: false,
uninstallSuccessful: false,
},
},
[UNINSTALLED]: {
target: UNINSTALLED,
},
[ERROR]: {
target: INSTALLABLE,
effects: {
installFailed: true,
},
},
},
},
[PRE_INSTALLED]: {
on: {
[UPDATE_EVENT]: {
target: UPDATING,
effects: {
updateFailed: false,
updateSuccessful: false,
},
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
},
[UNINSTALL_EVENT]: {
target: UNINSTALLING,
effects: {
uninstallFailed: false,
uninstallSuccessful: false,
},
},
},
},
[UPDATING]: {
on: {
[UPDATED]: {
target: INSTALLED,
effects: {
updateSuccessful: true,
},
},
[UPDATE_ERRORED]: {
target: INSTALLED,
effects: {
updateFailed: true,
},
},
},
},
[UNINSTALLING]: {
on: {
[INSTALLABLE]: {
target: INSTALLABLE,
effects: {
uninstallSuccessful: true,
},
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
effects: {
uninstallSuccessful: true,
},
},
[UNINSTALL_ERRORED]: {
target: INSTALLED,
effects: {
uninstallFailed: true,
},
},
},
},
[UNINSTALLED]: {
on: {
[INSTALLED]: {
target: INSTALLED,
},
[ERROR]: {
target: INSTALLABLE,
effects: {
installFailed: true,
},
},
},
},
};
/**
* Determines an application new state based on the application current state
* and an event. If the application current state cannot handle a given event,
* the current state is returned.
*
* @param {*} application
* @param {*} event
*/
const transitionApplicationState = (application, event) => {
const stateMachine = applicationStateMachine[application.status];
const newState = stateMachine !== undefined ? stateMachine.on[event] : false;
return newState
? {
...application,
status: newState.target,
...newState.effects,
}
: application;
};
export default transitionApplicationState;
......@@ -3,38 +3,12 @@ import axios from '../../lib/utils/axios_utils';
export default class ClusterService {
constructor(options = {}) {
this.options = options;
this.appInstallEndpointMap = {
helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint,
cert_manager: this.options.installCertManagerEndpoint,
crossplane: this.options.installCrossplaneEndpoint,
runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
elastic_stack: this.options.installElasticStackEndpoint,
};
this.appUpdateEndpointMap = {
knative: this.options.updateKnativeEndpoint,
};
}
fetchClusterStatus() {
return axios.get(this.options.endpoint);
}
installApplication(appId, params) {
return axios.post(this.appInstallEndpointMap[appId], params);
}
updateApplication(appId, params) {
return axios.patch(this.appUpdateEndpointMap[appId], params);
}
uninstallApplication(appId, params) {
return axios.delete(this.appInstallEndpointMap[appId], params);
}
fetchClusterEnvironments() {
return axios.get(this.options.clusterEnvironmentsEndpoint);
}
......
import { parseBoolean } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import {
INGRESS,
JUPYTER,
KNATIVE,
CERT_MANAGER,
CROSSPLANE,
RUNNER,
APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS,
INSTALL_EVENT,
UPDATE_EVENT,
UNINSTALL_EVENT,
ELASTIC_STACK,
} from '../constants';
import transitionApplicationState from '../services/application_state_machine';
const isApplicationInstalled = (appStatus) => APPLICATION_INSTALLED_STATUSES.includes(appStatus);
const applicationInitialState = {
status: null,
statusReason: null,
requestReason: null,
installable: true,
installed: false,
installFailed: false,
uninstallable: false,
uninstallFailed: false,
uninstallSuccessful: false,
validationError: null,
};
export default class ClusterStore {
constructor() {
this.state = {
helpPath: null,
helmHelpPath: null,
ingressHelpPath: null,
environmentsHelpPath: null,
clustersHelpPath: null,
deployBoardsHelpPath: null,
cloudRunHelpPath: null,
status: null,
providerType: null,
preInstalledKnative: false,
rbac: false,
statusReason: null,
applications: {
helm: {
...applicationInitialState,
title: s__('ClusterIntegration|Legacy Helm Tiller server'),
},
ingress: {
...applicationInitialState,
title: s__('ClusterIntegration|Ingress'),
externalIp: null,
externalHostname: null,
updateFailed: false,
updateAvailable: false,
},
cert_manager: {
...applicationInitialState,
title: s__('ClusterIntegration|Cert-Manager'),
email: null,
},
crossplane: {
...applicationInitialState,
title: s__('ClusterIntegration|Crossplane'),
stack: null,
},
runner: {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
version: null,
chartRepo: 'https://gitlab.com/gitlab-org/charts/gitlab-runner',
updateAvailable: null,
updateSuccessful: false,
updateFailed: false,
},
prometheus: {
...applicationInitialState,
title: s__('ClusterIntegration|Prometheus'),
},
jupyter: {
...applicationInitialState,
title: s__('ClusterIntegration|JupyterHub'),
hostname: null,
},
knative: {
...applicationInitialState,
title: s__('ClusterIntegration|Knative'),
hostname: null,
isEditingDomain: false,
externalIp: null,
externalHostname: null,
updateSuccessful: false,
updateFailed: false,
},
elastic_stack: {
...applicationInitialState,
title: s__('ClusterIntegration|Elastic Stack'),
},
cilium: {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Container Network Policies'),
installable: false,
},
},
environments: [],
fetchingEnvironments: false,
};
......@@ -118,10 +22,6 @@ export default class ClusterStore {
});
}
setManagePrometheusPath(managePrometheusPath) {
this.state.managePrometheusPath = managePrometheusPath;
}
updateStatus(status) {
this.state.status = status;
}
......@@ -130,10 +30,6 @@ export default class ClusterStore {
this.state.providerType = providerType;
}
updatePreInstalledKnative(preInstalledKnative) {
this.state.preInstalledKnative = parseBoolean(preInstalledKnative);
}
updateRbac(rbac) {
this.state.rbac = parseBoolean(rbac);
}
......@@ -142,112 +38,9 @@ export default class ClusterStore {
this.state.statusReason = reason;
}
installApplication(appId) {
this.handleApplicationEvent(appId, INSTALL_EVENT);
}
notifyInstallFailure(appId) {
this.handleApplicationEvent(appId, APPLICATION_STATUS.ERROR);
}
updateApplication(appId) {
this.handleApplicationEvent(appId, UPDATE_EVENT);
}
notifyUpdateFailure(appId) {
this.handleApplicationEvent(appId, APPLICATION_STATUS.UPDATE_ERRORED);
}
uninstallApplication(appId) {
this.handleApplicationEvent(appId, UNINSTALL_EVENT);
}
notifyUninstallFailure(appId) {
this.handleApplicationEvent(appId, APPLICATION_STATUS.UNINSTALL_ERRORED);
}
handleApplicationEvent(appId, event) {
const currentAppState = this.state.applications[appId];
this.state.applications[appId] = transitionApplicationState(currentAppState, event);
}
updateAppProperty(appId, prop, value) {
this.state.applications[appId][prop] = value;
}
updateStateFromServer(serverState = {}) {
this.state.status = serverState.status;
this.state.statusReason = serverState.status_reason;
serverState.applications.forEach((serverAppEntry) => {
const {
name: appId,
status,
status_reason: statusReason,
version,
update_available: updateAvailable,
can_uninstall: uninstallable,
} = serverAppEntry;
const currentApplicationState = this.state.applications[appId] || {};
const nextApplicationState = transitionApplicationState(currentApplicationState, status);
this.state.applications[appId] = {
...currentApplicationState,
...nextApplicationState,
statusReason,
installed: isApplicationInstalled(nextApplicationState.status),
uninstallable,
};
if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname;
this.state.applications.ingress.updateAvailable = updateAvailable;
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === CROSSPLANE) {
this.state.applications.crossplane.stack =
this.state.applications.crossplane.stack || serverAppEntry.stack;
} else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname = this.updateHostnameIfUnset(
this.state.applications.jupyter.hostname,
serverAppEntry.hostname,
'jupyter',
);
} else if (appId === KNATIVE) {
if (serverAppEntry.available_domains) {
this.state.applications.knative.availableDomains = serverAppEntry.available_domains;
}
if (!this.state.applications.knative.isEditingDomain) {
this.state.applications.knative.pagesDomain =
serverAppEntry.pages_domain || this.state.applications.knative.pagesDomain;
this.state.applications.knative.hostname =
serverAppEntry.hostname || this.state.applications.knative.hostname;
}
this.state.applications.knative.externalIp =
serverAppEntry.external_ip || this.state.applications.knative.externalIp;
this.state.applications.knative.externalHostname =
serverAppEntry.external_hostname || this.state.applications.knative.externalHostname;
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
this.state.applications.runner.updateAvailable = updateAvailable;
} else if (appId === ELASTIC_STACK) {
this.state.applications.elastic_stack.version = version;
this.state.applications.elastic_stack.updateAvailable = updateAvailable;
}
});
}
updateHostnameIfUnset(current, updated, fallback) {
return (
current ||
updated ||
(this.state.applications.ingress.externalIp
? `${fallback}.${this.state.applications.ingress.externalIp}.nip.io`
: '')
);
}
toggleFetchEnvironments(isFetching) {
......
.cluster-applications-table#js-cluster-applications
- active = params[:tab] == 'apps'
%li.nav-item{ role: 'presentation' }
%a#cluster-apps-tab.nav-link.qa-applications{ class: active_when(active), href: clusterable.cluster_path(@cluster.id, params: {tab: 'apps'}) }
%span= _('Applications')
......@@ -2,21 +2,10 @@
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
- breadcrumb_title @cluster.name
- page_title _('Kubernetes Cluster')
- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
- cluster_environments_path = clusterable.environments_cluster_path(@cluster)
- status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm),
install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress),
install_cert_manager_path: clusterable.install_applications_cluster_path(@cluster, :cert_manager),
install_crossplane_path: clusterable.install_applications_cluster_path(@cluster, :crossplane),
install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
install_elastic_stack_path: clusterable.install_applications_cluster_path(@cluster, :elastic_stack),
cluster_environments_path: cluster_environments_path,
toggle_status: @cluster.enabled? ? 'true': 'false',
has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false',
......@@ -24,15 +13,11 @@
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
provider_type: @cluster.provider_type,
pre_installed_knative: @cluster.knative_pre_installed? ? 'true': 'false',
help_path: help_page_path('user/project/clusters/index.md'),
environments_help_path: help_page_path('ci/environments/index.md', anchor: 'create-a-static-environment'),
clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'),
deploy_boards_help_path: help_page_path('user/project/deploy_boards.md', anchor: 'enabling-deploy-boards'),
cloud_run_help_path: help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'),
manage_prometheus_path: manage_prometheus_path,
cluster_id: @cluster.id,
cilium_help_path: help_page_path('user/clusters/applications.md', anchor: 'install-cilium-using-gitlab-cicd')} }
cluster_id: @cluster.id } }
.js-cluster-application-notice
.flash-container
......
......@@ -3402,9 +3402,6 @@ msgstr ""
msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
msgstr ""
 
msgid "Amazon Web Services"
msgstr ""
msgid "Amazon Web Services Logo"
msgstr ""
 
......@@ -6928,21 +6925,9 @@ msgstr ""
msgid "ClusterApplicationsRemoved|One-click application management was removed in GitLab 14.0. Your applications are still installed in your cluster, and integrations continue working."
msgstr ""
 
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|%{boldStart}Note:%{boldEnd} Requires Ingress to be installed."
msgstr ""
msgid "ClusterIntegration|%{linkStart}More information%{linkEnd}"
msgstr ""
 
msgid "ClusterIntegration|%{title} uninstalled successfully."
msgstr ""
msgid "ClusterIntegration|%{title} updated successfully."
msgstr ""
msgid "ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes %{code_open}cluster-admin%{code_close} privileges."
msgstr ""
 
......@@ -6976,12 +6961,6 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster’s integration"
msgstr ""
 
msgid "ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored."
msgstr ""
msgid "ClusterIntegration|All data will be deleted and cannot be restored."
msgstr ""
msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{linkStart}More information%{linkEnd}"
msgstr ""
 
......@@ -7015,9 +6994,6 @@ msgstr ""
msgid "ClusterIntegration|Any project namespaces"
msgstr ""
 
msgid "ClusterIntegration|Any running pipelines will be canceled."
msgstr ""
msgid "ClusterIntegration|Apply for credit"
msgstr ""
 
......@@ -7036,15 +7012,6 @@ msgstr ""
msgid "ClusterIntegration|CA Certificate"
msgstr ""
 
msgid "ClusterIntegration|Can be safely removed. Prior to GitLab 13.2, GitLab used a remote Tiller server to manage the applications. GitLab no longer uses this server. Uninstalling this server will not affect your other applications. This row will disappear afterwards."
msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{linkStart}Let's Encrypt%{linkEnd} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
 
......@@ -7066,9 +7033,6 @@ msgstr ""
msgid "ClusterIntegration|Choose the worker node %{linkStart}instance type%{linkEnd}."
msgstr ""
 
msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster."
msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
 
......@@ -7108,15 +7072,6 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
 
msgid "ClusterIntegration|Copy Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname"
msgstr ""
msgid "ClusterIntegration|Copy Knative Endpoint"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
 
......@@ -7165,12 +7120,6 @@ msgstr ""
msgid "ClusterIntegration|Creating Kubernetes cluster"
msgstr ""
 
msgid "ClusterIntegration|Crossplane"
msgstr ""
msgid "ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{codeStart}kubectl%{codeEnd} or %{linkStart}GitLab Integration%{linkEnd}. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on."
msgstr ""
msgid "ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal"
msgstr ""
 
......@@ -7186,9 +7135,6 @@ msgstr ""
msgid "ClusterIntegration|Elastic Kubernetes Service"
msgstr ""
 
msgid "ClusterIntegration|Elastic Stack"
msgstr ""
msgid "ClusterIntegration|Enable Cloud Run for Anthos"
msgstr ""
 
......@@ -7204,9 +7150,6 @@ msgstr ""
msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
msgstr ""
 
msgid "ClusterIntegration|Enabled stack"
msgstr ""
msgid "ClusterIntegration|Enter new Service Token"
msgstr ""
 
......@@ -7252,18 +7195,9 @@ msgstr ""
msgid "ClusterIntegration|GitLab Agent managed clusters"
msgstr ""
 
msgid "ClusterIntegration|GitLab Container Network Policies"
msgstr ""
msgid "ClusterIntegration|GitLab Integration"
msgstr ""
 
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|GitLab failed to authenticate."
msgstr ""
 
......@@ -7300,18 +7234,6 @@ msgstr ""
msgid "ClusterIntegration|In order to view the health of your cluster, you must first enable Prometheus in the Integrations tab."
msgstr ""
 
msgid "ClusterIntegration|Ingress"
msgstr ""
msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{linkStart}pricing%{linkEnd}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr ""
 
......@@ -7333,39 +7255,9 @@ msgstr ""
msgid "ClusterIntegration|Integrations allow you to use applications installed in your cluster as part of your GitLab workflow."
msgstr ""
 
msgid "ClusterIntegration|Issuer Email"
msgstr ""
msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer."
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
msgstr ""
msgid "ClusterIntegration|JupyterHub"
msgstr ""
msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
msgstr ""
msgid "ClusterIntegration|Key pair name"
msgstr ""
 
msgid "ClusterIntegration|Knative"
msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative domain name was updated successfully."
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
msgstr ""
msgid "ClusterIntegration|Kubernetes cluster is being created..."
msgstr ""
 
......@@ -7399,9 +7291,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
 
msgid "ClusterIntegration|Legacy Helm Tiller server"
msgstr ""
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
 
......@@ -7492,9 +7381,6 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
 
msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
 
......@@ -7504,15 +7390,6 @@ msgstr ""
msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
msgstr ""
 
msgid "ClusterIntegration|Prometheus"
msgstr ""
msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{linkStart}GitLab Integration%{linkEnd} to monitor deployed applications."
msgstr ""
msgid "ClusterIntegration|Protect your clusters with GitLab Container Network Policies by enforcing how pods communicate with each other and other network endpoints. %{linkStart}Learn more about configuring Network Policies here.%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Provider details"
msgstr ""
 
......@@ -7549,15 +7426,6 @@ msgstr ""
msgid "ClusterIntegration|Removes cluster from project but keeps associated resources"
msgstr ""
 
msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
msgid "ClusterIntegration|Request to begin uninstalling failed"
msgstr ""
msgid "ClusterIntegration|Save changes"
msgstr ""
 
......@@ -7570,9 +7438,6 @@ msgstr ""
msgid "ClusterIntegration|Search VPCs"
msgstr ""
 
msgid "ClusterIntegration|Search domains"
msgstr ""
msgid "ClusterIntegration|Search instance types"
msgstr ""
 
......@@ -7621,15 +7486,9 @@ msgstr ""
msgid "ClusterIntegration|Select a region to choose a VPC"
msgstr ""
 
msgid "ClusterIntegration|Select a stack to install Crossplane."
msgstr ""
msgid "ClusterIntegration|Select a zone to choose a network"
msgstr ""
 
msgid "ClusterIntegration|Select existing domain or use new"
msgstr ""
msgid "ClusterIntegration|Select machine type"
msgstr ""
 
......@@ -7672,15 +7531,6 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster"
msgstr ""
 
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
msgstr ""
msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{linkStart}Auto DevOps.%{linkEnd} The domain should have a wildcard DNS configured matching the domain. "
msgstr ""
 
......@@ -7696,24 +7546,6 @@ msgstr ""
msgid "ClusterIntegration|The URL used to access the Kubernetes API."
msgstr ""
 
msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
msgstr ""
msgid "ClusterIntegration|The associated Tiller pod will be deleted and cannot be restored. Your other applications will remain unaffected."
msgstr ""
msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
msgstr ""
msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
msgstr ""
msgid "ClusterIntegration|The elastic stack collects logs from all pods in your cluster"
msgstr ""
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals."
msgstr ""
 
......@@ -7744,9 +7576,6 @@ msgstr ""
msgid "ClusterIntegration|This will permanently delete the following resources:"
msgstr ""
 
msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
msgstr ""
msgid "ClusterIntegration|To create a cluster, first create a project on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}."
msgstr ""
 
......@@ -7768,21 +7597,9 @@ msgstr ""
msgid "ClusterIntegration|Unable to Connect"
msgstr ""
 
msgid "ClusterIntegration|Uninstall %{appTitle}"
msgstr ""
msgid "ClusterIntegration|Unknown Error"
msgstr ""
 
msgid "ClusterIntegration|Update %{appTitle}"
msgstr ""
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr ""
msgid "ClusterIntegration|Use %{query}"
msgstr ""
msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
msgstr ""
 
......@@ -7807,27 +7624,12 @@ msgstr ""
msgid "ClusterIntegration|You are about to remove your cluster integration."
msgstr ""
 
msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
msgstr ""
msgid "ClusterIntegration|You are about to update %{appTitle} on your cluster."
msgstr ""
msgid "ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN."
msgstr ""
 
msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
msgstr ""
msgid "ClusterIntegration|You must specify a domain before you can install Knative."
msgstr ""
msgid "ClusterIntegration|You should select at least two subnets"
msgstr ""
 
msgid "ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days."
msgstr ""
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
 
......@@ -7843,9 +7645,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
 
msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
 
......@@ -9654,9 +9453,6 @@ msgstr ""
msgid "Cron time zone"
msgstr ""
 
msgid "Crossplane"
msgstr ""
msgid "Crowd"
msgstr ""
 
......@@ -13452,9 +13248,6 @@ msgstr ""
msgid "ExternalWikiService|https://example.com/xxx/wiki/..."
msgstr ""
 
msgid "Externally installed"
msgstr ""
msgid "Facebook"
msgstr ""
 
......@@ -15403,9 +15196,6 @@ msgstr ""
msgid "Goal of the changes and what reviewers should be aware of"
msgstr ""
 
msgid "Google Cloud Platform"
msgstr ""
msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr ""
 
......@@ -17579,9 +17369,6 @@ msgstr ""
msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)."
msgstr ""
 
msgid "Install"
msgstr ""
msgid "Install GitLab Runner and ensure it's running."
msgstr ""
 
......@@ -17594,12 +17381,6 @@ msgstr ""
msgid "Installation"
msgstr ""
 
msgid "Installed"
msgstr ""
msgid "Installing"
msgstr ""
msgid "Instance"
msgid_plural "Instances"
msgstr[0] ""
......@@ -19979,9 +19760,6 @@ msgstr ""
msgid "Makes this issue confidential."
msgstr ""
 
msgid "Manage"
msgstr ""
msgid "Manage Web IDE features."
msgstr ""
 
......@@ -21148,9 +20926,6 @@ msgstr ""
msgid "Mi"
msgstr ""
 
msgid "Microsoft Azure"
msgstr ""
msgid "Middleman project with Static Site Editor support"
msgstr ""
 
......@@ -28044,9 +27819,6 @@ msgstr ""
msgid "Retry this job in order to create the necessary resources."
msgstr ""
 
msgid "Retry update"
msgstr ""
msgid "Retry verification"
msgstr ""
 
......@@ -28153,9 +27925,6 @@ msgstr ""
msgid "Rollback"
msgstr ""
 
msgid "Rook"
msgstr ""
msgid "Ruby"
msgstr ""
 
......@@ -29324,9 +29093,6 @@ msgstr ""
msgid "Select Page"
msgstr ""
 
msgid "Select Stack"
msgstr ""
msgid "Select a branch"
msgstr ""
 
......@@ -34869,12 +34635,6 @@ msgstr ""
msgid "Unhappy?"
msgstr ""
 
msgid "Uninstall"
msgstr ""
msgid "Uninstalling"
msgstr ""
msgid "Units|ms"
msgstr ""
 
......@@ -35100,9 +34860,6 @@ msgstr ""
msgid "Updated %{updated_at} by %{updated_by}"
msgstr ""
 
msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
msgid "Updates"
msgstr ""
 
......@@ -37596,9 +37353,6 @@ msgstr ""
msgid "You must provide your current password in order to change it."
msgstr ""
 
msgid "You must select a stack for configuring your cloud provider. Learn more about"
msgstr ""
msgid "You must solve the CAPTCHA in order to submit"
msgstr ""
 
......
......@@ -6,10 +6,6 @@ module Project
module Infrastructure
module Kubernetes
class Show < Page::Base
view 'app/assets/javascripts/clusters/components/applications.vue' do
element :ingress_ip_address, 'id="ingress-endpoint"' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/clusters/forms/components/integration_form.vue' do
element :integration_status_toggle, required: true
element :base_domain_field, required: true
......@@ -20,15 +16,6 @@ class Show < Page::Base
element :details, required: true
end
view 'app/views/clusters/clusters/_applications_tab.html.haml' do
element :applications, required: true
end
view 'app/assets/javascripts/clusters/components/application_row.vue' do
element :install_button
element :uninstall_button
end
view 'app/views/clusters/clusters/_health.html.haml' do
element :cluster_health_section
end
......@@ -42,36 +29,6 @@ def open_details
click_element :details
end
def open_applications
has_element?(:applications, wait: 30)
click_element :applications
end
def install!(application_name)
within_element(application_name) do
has_element?(:install_button, application: application_name, wait: 30)
click_element :install_button
end
end
def await_installed(application_name)
within_element(application_name) do
has_element?(:uninstall_button, application: application_name, wait: 300, skip_finished_loading_check: true)
end
end
def has_application_installed?(application_name)
within_element(application_name) do
has_element?(:uninstall_button, application: application_name, wait: 300)
end
end
def ingress_ip
# We need to wait longer since it can take some time before the
# ip address is assigned for the ingress controller
page.find('#ingress-endpoint', wait: 1200).value
end
def set_domain(domain)
fill_element :base_domain_field, domain
end
......
......@@ -3,6 +3,8 @@
module QA
module Resource
module KubernetesCluster
# TODO: This resource is currently broken, since one-click apps have been removed.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/333818
class ProjectCluster < Base
attr_writer :cluster,
:install_ingress, :install_prometheus, :install_runner, :domain
......@@ -40,6 +42,8 @@ def fabricate!
# We must wait a few seconds for permissions to be set up correctly for new cluster
sleep 25
# TODO: These steps do not work anymore, see https://gitlab.com/gitlab-org/gitlab/-/issues/333818
# Open applications tab
show.open_applications
......
......@@ -2,15 +2,12 @@ import MockAdapter from 'axios-mock-adapter';
import { loadHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout';
import Clusters from '~/clusters/clusters_bundle';
import { APPLICATION_STATUS, APPLICATIONS, RUNNER } from '~/clusters/constants';
import axios from '~/lib/utils/axios_utils';
import initProjectSelectDropdown from '~/project_select';
jest.mock('~/lib/utils/poll');
jest.mock('~/project_select');
const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS;
describe('Clusters', () => {
setTestTimeout(1000);
......@@ -57,67 +54,6 @@ describe('Clusters', () => {
});
});
describe('checkForNewInstalls', () => {
const INITIAL_APP_MAP = {
helm: { status: null, title: 'Helm Tiller' },
ingress: { status: null, title: 'Ingress' },
runner: { status: null, title: 'GitLab Runner' },
};
it('does not show alert when things transition from initial null state to something', () => {
cluster.checkForNewInstalls(INITIAL_APP_MAP, {
...INITIAL_APP_MAP,
helm: { status: INSTALLABLE, title: 'Helm Tiller' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
expect(flashMessage).toBeNull();
});
it('shows an alert when something gets newly installed', () => {
cluster.checkForNewInstalls(
{
...INITIAL_APP_MAP,
helm: { status: INSTALLING, title: 'Helm Tiller' },
},
{
...INITIAL_APP_MAP,
helm: { status: INSTALLED, title: 'Helm Tiller' },
},
);
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
expect(flashMessage).not.toBeNull();
expect(flashMessage.textContent.trim()).toEqual(
'Helm Tiller was successfully installed on your Kubernetes cluster',
);
});
it('shows an alert when multiple things gets newly installed', () => {
cluster.checkForNewInstalls(
{
...INITIAL_APP_MAP,
helm: { status: INSTALLING, title: 'Helm Tiller' },
ingress: { status: INSTALLABLE, title: 'Ingress' },
},
{
...INITIAL_APP_MAP,
helm: { status: INSTALLED, title: 'Helm Tiller' },
ingress: { status: INSTALLED, title: 'Ingress' },
},
);
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
expect(flashMessage).not.toBeNull();
expect(flashMessage.textContent.trim()).toEqual(
'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster',
);
});
});
describe('updateContainer', () => {
const { location } = window;
......@@ -237,77 +173,6 @@ describe('Clusters', () => {
});
});
describe('installApplication', () => {
it.each(APPLICATIONS)('tries to install %s', (applicationId, done) => {
jest.spyOn(cluster.service, 'installApplication').mockResolvedValue();
cluster.store.state.applications[applicationId].status = INSTALLABLE;
const params = {};
if (applicationId === 'knative') {
params.hostname = 'test-example.com';
}
// eslint-disable-next-line promise/valid-params
cluster
.installApplication({ id: applicationId, params })
.then(() => {
expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING);
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, params);
done();
})
.catch();
});
it('sets error request status when the request fails', () => {
jest
.spyOn(cluster.service, 'installApplication')
.mockRejectedValueOnce(new Error('STUBBED ERROR'));
cluster.store.state.applications.helm.status = INSTALLABLE;
const promise = cluster.installApplication({ id: 'helm' });
return promise.then(() => {
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE);
expect(cluster.store.state.applications.helm.installFailed).toBe(true);
expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
});
});
});
describe('uninstallApplication', () => {
it.each(APPLICATIONS)('tries to uninstall %s', (applicationId) => {
jest.spyOn(cluster.service, 'uninstallApplication').mockResolvedValueOnce();
cluster.store.state.applications[applicationId].status = INSTALLED;
cluster.uninstallApplication({ id: applicationId });
expect(cluster.store.state.applications[applicationId].status).toEqual(UNINSTALLING);
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
expect(cluster.service.uninstallApplication).toHaveBeenCalledWith(applicationId);
});
it('sets error request status when the uninstall request fails', () => {
jest
.spyOn(cluster.service, 'uninstallApplication')
.mockRejectedValueOnce(new Error('STUBBED ERROR'));
cluster.store.state.applications.helm.status = INSTALLED;
const promise = cluster.uninstallApplication({ id: 'helm' });
return promise.then(() => {
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED);
expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true);
expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
});
});
});
describe('fetch cluster environments success', () => {
beforeEach(() => {
jest.spyOn(cluster.store, 'toggleFetchEnvironments').mockReturnThis();
......@@ -328,7 +193,6 @@ describe('Clusters', () => {
describe('handleClusterStatusSuccess', () => {
beforeEach(() => {
jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis();
jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis();
jest.spyOn(cluster, 'updateContainer').mockReturnThis();
cluster.handleClusterStatusSuccess({ data: {} });
});
......@@ -337,38 +201,8 @@ describe('Clusters', () => {
expect(cluster.store.updateStateFromServer).toHaveBeenCalled();
});
it('checks for new installable apps', () => {
expect(cluster.checkForNewInstalls).toHaveBeenCalled();
});
it('updates message containers', () => {
expect(cluster.updateContainer).toHaveBeenCalled();
});
});
describe('updateApplication', () => {
const params = { version: '1.0.0' };
let storeUpdateApplication;
let installApplication;
beforeEach(() => {
storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication');
installApplication = jest.spyOn(cluster.service, 'installApplication');
cluster.updateApplication({ id: RUNNER, params });
});
afterEach(() => {
storeUpdateApplication.mockRestore();
installApplication.mockRestore();
});
it('calls store updateApplication method', () => {
expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER);
});
it('sends installApplication request', () => {
expect(installApplication).toHaveBeenCalledWith(RUNNER, params);
});
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Applications Cert-Manager application shows the correct description 1`] = `
<p
data-testid="certManagerDescription"
>
Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by
<a
class="gl-link"
href="https://letsencrypt.org/"
rel="noopener noreferrer"
target="_blank"
>
Let's Encrypt
</a>
and ensure that certificates are valid and up-to-date.
</p>
`;
exports[`Applications Cilium application shows the correct description 1`] = `
<p
data-testid="ciliumDescription"
>
Protect your clusters with GitLab Container Network Policies by enforcing how pods communicate with each other and other network endpoints.
<a
class="gl-link"
href="cilium-help-path"
rel="noopener"
target="_blank"
>
Learn more about configuring Network Policies here.
</a>
</p>
`;
exports[`Applications Crossplane application shows the correct description 1`] = `
<p
data-testid="crossplaneDescription"
>
Crossplane enables declarative provisioning of managed services from your cloud of choice using
<code>
kubectl
</code>
or
<a
class="gl-link"
href="https://docs.gitlab.com/ee/user/clusters/applications.html#crossplane"
rel="noopener noreferrer"
target="_blank"
>
GitLab Integration
</a>
. Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on.
</p>
`;
exports[`Applications Ingress application shows the correct warning message 1`] = `
<span
data-testid="ingressCostWarning"
>
Installing Ingress may incur additional costs. Learn more about
<a
class="gl-link"
href="https://cloud.google.com/compute/pricing#lb"
rel="noopener noreferrer"
target="_blank"
>
pricing
</a>
.
</span>
`;
exports[`Applications Knative application shows the correct description 1`] = `
<span
data-testid="installed-via"
>
installed via
<a
class="gl-link"
href=""
rel="noopener"
target="_blank"
>
Cloud Run
</a>
</span>
`;
exports[`Applications Prometheus application shows the correct description 1`] = `
<span
data-testid="prometheusDescription"
>
Prometheus is an open-source monitoring system with
<a
class="gl-link"
href="https://docs.gitlab.com/ee/user/project/integrations/prometheus.html"
rel="noopener noreferrer"
target="_blank"
>
GitLab Integration
</a>
to monitor deployed applications.
</span>
`;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment