Commit 994a31e8 authored by Zamir Martins Filho's avatar Zamir Martins Filho Committed by Kushal Pandya

Replace checkbox by toggle for modsecurity setup

In addition to the replacement, icon and title
have also been added. This change affects the
cluster app page. Modsecurity can be found
under Ingress
parent cc9eb19a
...@@ -256,6 +256,7 @@ export default class Clusters { ...@@ -256,6 +256,7 @@ export default class Clusters {
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data)); eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data)); eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data));
eventHub.$on('resetIngressModSecurityEnabled', id => this.resetIngressModSecurityEnabled(id));
// Add event listener to all the banner close buttons // Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable'); this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure'); this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
...@@ -270,6 +271,7 @@ export default class Clusters { ...@@ -270,6 +271,7 @@ export default class Clusters {
eventHub.$off('setCrossplaneProviderStack'); eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication'); eventHub.$off('uninstallApplication');
eventHub.$off('setIngressModSecurityEnabled'); eventHub.$off('setIngressModSecurityEnabled');
eventHub.$off('resetIngressModSecurityEnabled');
} }
initPolling(method, successCallback, errorCallback) { initPolling(method, successCallback, errorCallback) {
...@@ -523,6 +525,10 @@ export default class Clusters { ...@@ -523,6 +525,10 @@ export default class Clusters {
this.store.updateAppProperty(id, 'modsecurity_enabled', modSecurityEnabled); this.store.updateAppProperty(id, 'modsecurity_enabled', modSecurityEnabled);
} }
resetIngressModSecurityEnabled(id) {
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', false);
}
destroy() { destroy() {
this.destroyed = true; this.destroyed = true;
......
...@@ -119,9 +119,6 @@ export default { ...@@ -119,9 +119,6 @@ export default {
ingressInstalled() { ingressInstalled() {
return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED; return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
}, },
ingressEnableModsecurity() {
return this.applications.ingress.modsecurity_enabled;
},
ingressExternalEndpoint() { ingressExternalEndpoint() {
return this.applications.ingress.externalIp || this.applications.ingress.externalHostname; return this.applications.ingress.externalIp || this.applications.ingress.externalHostname;
}, },
......
<script> <script>
import _ from 'lodash'; import _ from 'lodash';
import { __ } from '../../locale'; import { __ } from '../../locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants'; import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; import { GlAlert, GlSprintf, GlLink, GlToggle, GlButton } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
import modSecurityLogo from 'images/cluster_app_logos/modsecurity.png';
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS; const { UPDATING, UNINSTALLING, INSTALLING, INSTALLED, UPDATED } = APPLICATION_STATUS;
export default { export default {
title: 'ModSecurity Web Application Firewall',
modsecurityUrl: 'https://modsecurity.org/about.html',
components: { components: {
LoadingButton,
GlAlert, GlAlert,
GlSprintf, GlSprintf,
GlLink, GlLink,
GlToggle,
GlButton,
}, },
props: { props: {
ingress: { ingress: {
...@@ -26,6 +29,10 @@ export default { ...@@ -26,6 +29,10 @@ export default {
default: '', default: '',
}, },
}, },
data: () => ({
modSecurityLogo,
hasValueChanged: false,
}),
computed: { computed: {
modSecurityEnabled: { modSecurityEnabled: {
get() { get() {
...@@ -36,6 +43,11 @@ export default { ...@@ -36,6 +43,11 @@ export default {
id: INGRESS, id: INGRESS,
modSecurityEnabled: isEnabled, modSecurityEnabled: isEnabled,
}); });
if (this.hasValueChanged) {
this.resetStatus();
} else {
this.hasValueChanged = true;
}
}, },
}, },
ingressModSecurityDescription() { ingressModSecurityDescription() {
...@@ -45,13 +57,21 @@ export default { ...@@ -45,13 +57,21 @@ export default {
return [UPDATING].includes(this.ingress.status); return [UPDATING].includes(this.ingress.status);
}, },
saveButtonDisabled() { saveButtonDisabled() {
return [UNINSTALLING, UPDATING].includes(this.ingress.status); return [UNINSTALLING, UPDATING, INSTALLING].includes(this.ingress.status);
}, },
saveButtonLabel() { saveButtonLabel() {
return this.saving ? __('Saving') : __('Save changes'); return this.saving ? __('Saving') : __('Save changes');
}, },
ingressInstalled() { /**
return this.ingress.installed; * Returns true either when:
* - The application is getting updated.
* - The user has changed some of the settings for an application which is
* neither getting installed nor updated.
*/
showButtons() {
return (
this.saving || (this.hasValueChanged && [INSTALLED, UPDATED].includes(this.ingress.status))
);
}, },
}, },
methods: { methods: {
...@@ -60,6 +80,11 @@ export default { ...@@ -60,6 +80,11 @@ export default {
id: INGRESS, id: INGRESS,
params: { modsecurity_enabled: this.ingress.modsecurity_enabled }, params: { modsecurity_enabled: this.ingress.modsecurity_enabled },
}); });
this.resetStatus();
},
resetStatus() {
eventHub.$emit('resetIngressModSecurityEnabled', INGRESS);
this.hasValueChanged = false;
}, },
}, },
}; };
...@@ -75,25 +100,32 @@ export default { ...@@ -75,25 +100,32 @@ export default {
@dismiss="alert = null" @dismiss="alert = null"
> >
{{ {{
s__('ClusterIntegration|Something went wrong while updating the Web Application Firewall.') s__(
'ClusterIntegration|Something went wrong while trying to save your settings. Please try again.',
)
}} }}
</gl-alert> </gl-alert>
<div class="form-group"> <div class="gl-responsive-table-row-layout" role="row">
<div class="form-check form-check-inline"> <div class="table-section append-right-8 section-align-top" role="gridcell">
<input <img
v-model="modSecurityEnabled" :src="modSecurityLogo"
type="checkbox" :alt="`${$options.title} logo`"
autocomplete="off" class="cluster-application-logo avatar s40"
class="form-check-input"
/> />
<label class="form-check-label label-bold" for="ingress-enable-modsecurity">
{{ s__('ClusterIntegration|Enable Web Application Firewall') }}
</label>
</div> </div>
<div class="table-section section-wrap" role="gridcell">
<strong>
<gl-link :href="$options.modsecurityUrl" target="_blank">{{ $options.title }} </gl-link>
</strong>
<div class="form-group">
<p class="form-text text-muted"> <p class="form-text text-muted">
<strong> <strong>
<gl-sprintf <gl-sprintf
:message="s__('ClusterIntegration|Learn more about %{linkStart}ModSecurity%{linkEnd}')" :message="
s__(
'ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}',
)
"
> >
<template #link="{ content }"> <template #link="{ content }">
<gl-link :href="ingressModSecurityDescription" target="_blank" <gl-link :href="ingressModSecurityDescription" target="_blank"
...@@ -103,14 +135,30 @@ export default { ...@@ -103,14 +135,30 @@ export default {
</gl-sprintf> </gl-sprintf>
</strong> </strong>
</p> </p>
<loading-button <div class="form-check form-check-inline mt-3">
v-if="ingressInstalled" <gl-toggle
class="btn-success mt-1" v-model="modSecurityEnabled"
:label-on="__('Enabled')"
:label-off="__('Disabled')"
:disabled="saveButtonDisabled"
label-position="right"
/>
</div>
<div v-if="showButtons">
<gl-button
class="btn-success inline mr-1"
:loading="saving" :loading="saving"
:disabled="saveButtonDisabled" :disabled="saveButtonDisabled"
:label="saveButtonLabel"
@click="updateApplication" @click="updateApplication"
/> >
{{ saveButtonLabel }}
</gl-button>
<gl-button :disabled="saveButtonDisabled" @click="resetStatus">
{{ __('Cancel') }}
</gl-button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -211,9 +211,7 @@ export default class ClusterStore { ...@@ -211,9 +211,7 @@ export default class ClusterStore {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip; this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname; this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname;
if (!this.state.applications.ingress.isEditingModSecurityEnabled) { if (!this.state.applications.ingress.isEditingModSecurityEnabled) {
this.state.applications.ingress.modsecurity_enabled = this.state.applications.ingress.modsecurity_enabled = serverAppEntry.modsecurity_enabled;
serverAppEntry.modsecurity_enabled ||
this.state.applications.ingress.modsecurity_enabled;
} }
} else if (appId === CERT_MANAGER) { } else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email = this.state.applications.cert_manager.email =
......
...@@ -16,7 +16,7 @@ module Clusters ...@@ -16,7 +16,7 @@ module Clusters
include AfterCommitQueue include AfterCommitQueue
default_value_for :ingress_type, :nginx default_value_for :ingress_type, :nginx
default_value_for :modsecurity_enabled, false default_value_for :modsecurity_enabled, true
default_value_for :version, VERSION default_value_for :version, VERSION
enum ingress_type: { enum ingress_type: {
......
---
title: Replace checkbox by toggle for ModSecurity on Cluster App Page
merge_request: 26720
author:
type: changed
...@@ -4246,9 +4246,6 @@ msgstr "" ...@@ -4246,9 +4246,6 @@ msgstr ""
msgid "ClusterIntegration|Enable Cloud Run for Anthos" msgid "ClusterIntegration|Enable Cloud Run for Anthos"
msgstr "" msgstr ""
msgid "ClusterIntegration|Enable Web Application Firewall"
msgstr ""
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster." msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr "" msgstr ""
...@@ -4426,9 +4423,6 @@ msgstr "" ...@@ -4426,9 +4423,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}." msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Learn more about %{linkStart}ModSecurity%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}." msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
msgstr "" msgstr ""
...@@ -4561,6 +4555,9 @@ msgstr "" ...@@ -4561,6 +4555,9 @@ msgstr ""
msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration." msgid "ClusterIntegration|Read our %{link_start}help page%{link_end} on Kubernetes cluster integration."
msgstr "" msgstr ""
msgid "ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Region" msgid "ClusterIntegration|Region"
msgstr "" msgstr ""
...@@ -4708,13 +4705,13 @@ msgstr "" ...@@ -4708,13 +4705,13 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}" msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr "" msgstr ""
msgid "ClusterIntegration|Something went wrong while uninstalling %{title}" msgid "ClusterIntegration|Something went wrong while trying to save your settings. Please try again."
msgstr "" msgstr ""
msgid "ClusterIntegration|Something went wrong while updating Knative domain name." msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
msgstr "" msgstr ""
msgid "ClusterIntegration|Something went wrong while updating the Web Application Firewall." msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
msgstr "" msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain." msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue'; import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants'; import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import { GlAlert } from '@gitlab/ui'; import { GlAlert, GlToggle } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
const { UPDATING } = APPLICATION_STATUS; const { UPDATING } = APPLICATION_STATUS;
...@@ -27,18 +26,29 @@ describe('IngressModsecuritySettings', () => { ...@@ -27,18 +26,29 @@ describe('IngressModsecuritySettings', () => {
}); });
}; };
const findSaveButton = () => wrapper.find(LoadingButton); const findSaveButton = () => wrapper.find('.btn-success');
const findModSecurityCheckbox = () => wrapper.find('input').element; const findCancelButton = () => wrapper.find('[variant="secondary"]');
const findModSecurityToggle = () => wrapper.find(GlToggle);
describe('when ingress is installed', () => { describe('when ingress is installed', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ installed: true }); createComponent({ installed: true, status: 'installed' });
jest.spyOn(eventHub, '$emit'); jest.spyOn(eventHub, '$emit');
}); });
it('renders save button', () => { it('does not render save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
});
describe('with toggle changed by the user', () => {
beforeEach(() => {
findModSecurityToggle().vm.$emit('change');
});
it('renders both save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(true); expect(findSaveButton().exists()).toBe(true);
expect(findModSecurityCheckbox().checked).toBe(false); expect(findCancelButton().exists()).toBe(true);
}); });
describe('and the save changes button is clicked', () => { describe('and the save changes button is clicked', () => {
...@@ -46,13 +56,25 @@ describe('IngressModsecuritySettings', () => { ...@@ -46,13 +56,25 @@ describe('IngressModsecuritySettings', () => {
findSaveButton().vm.$emit('click'); findSaveButton().vm.$emit('click');
}); });
it('triggers save event and pass current modsecurity value', () => it('triggers save event and pass current modsecurity value', () => {
wrapper.vm.$nextTick().then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', { expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: INGRESS, id: INGRESS,
params: { modsecurity_enabled: false }, params: { modsecurity_enabled: false },
}); });
})); });
});
describe('and the cancel button is clicked', () => {
beforeEach(() => {
findCancelButton().vm.$emit('click');
});
it('triggers reset event and hides both cancel and save changes button', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('resetIngressModSecurityEnabled', INGRESS);
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
});
});
}); });
it('triggers set event to be propagated with the current modsecurity value', () => { it('triggers set event to be propagated with the current modsecurity value', () => {
...@@ -79,7 +101,7 @@ describe('IngressModsecuritySettings', () => { ...@@ -79,7 +101,7 @@ describe('IngressModsecuritySettings', () => {
}); });
it('renders save button with "Saving" label', () => { it('renders save button with "Saving" label', () => {
expect(findSaveButton().props('label')).toBe('Saving'); expect(findSaveButton().text()).toBe('Saving');
}); });
}); });
...@@ -101,7 +123,7 @@ describe('IngressModsecuritySettings', () => { ...@@ -101,7 +123,7 @@ describe('IngressModsecuritySettings', () => {
it('does not render the save button', () => { it('does not render the save button', () => {
expect(findSaveButton().exists()).toBe(false); expect(findSaveButton().exists()).toBe(false);
expect(findModSecurityCheckbox().checked).toBe(false); expect(findModSecurityToggle().props('value')).toBe(false);
}); });
}); });
}); });
...@@ -20,6 +20,7 @@ const CLUSTERS_MOCK_DATA = { ...@@ -20,6 +20,7 @@ const CLUSTERS_MOCK_DATA = {
external_ip: null, external_ip: null,
external_hostname: null, external_hostname: null,
can_uninstall: false, can_uninstall: false,
modsecurity_enabled: false,
}, },
{ {
name: 'runner', name: 'runner',
......
...@@ -177,6 +177,7 @@ describe Clusters::Applications::Ingress do ...@@ -177,6 +177,7 @@ describe Clusters::Applications::Ingress do
context 'when modsecurity_enabled is disabled' do context 'when modsecurity_enabled is disabled' do
before do before do
allow(subject).to receive(:cluster).and_return(cluster) allow(subject).to receive(:cluster).and_return(cluster)
allow(subject).to receive(:modsecurity_enabled).and_return(false)
end end
it 'excludes modsecurity module enablement' do it 'excludes modsecurity module enablement' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment