Commit cd3e2c7b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent c1fc5da1
......@@ -92,10 +92,10 @@ export default class TemplateSelector {
}
startLoadingSpinner() {
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
this.$dropdownIcon.addClass('spinner').removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('spinner');
}
}
......@@ -255,6 +255,7 @@ export default class Clusters {
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data));
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
......@@ -268,6 +269,7 @@ export default class Clusters {
eventHub.$off('setKnativeHostname');
eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication');
eventHub.$off('setIngressModSecurityEnabled');
}
initPolling(method, successCallback, errorCallback) {
......@@ -513,6 +515,11 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'validationError', null);
}
setIngressModSecurityEnabled({ id, modSecurityEnabled }) {
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', true);
this.store.updateAppProperty(id, 'modsecurity_enabled', modSecurityEnabled);
}
destroy() {
this.destroyed = true;
......
......@@ -21,6 +21,7 @@ import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
import eventHub from '~/clusters/event_hub';
import CrossplaneProviderStack from './crossplane_provider_stack.vue';
import IngressModsecuritySettings from './ingress_modsecurity_settings.vue';
export default {
components: {
......@@ -29,6 +30,7 @@ export default {
GlLoadingIcon,
KnativeDomainEditor,
CrossplaneProviderStack,
IngressModsecuritySettings,
},
props: {
type: {
......@@ -129,18 +131,6 @@ export default {
crossplaneInstalled() {
return this.applications.crossplane.status === APPLICATION_STATUS.INSTALLED;
},
ingressModSecurityDescription() {
const escapedUrl = _.escape(this.ingressModSecurityHelpPath);
return sprintf(
s__('ClusterIntegration|Learn more about %{startLink}ModSecurity%{endLink}'),
{
startLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
endLink: '</a>',
},
false,
);
},
ingressDescription() {
return sprintf(
_.escape(
......@@ -241,6 +231,9 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
}
return null;
},
ingress() {
return this.applications.ingress;
},
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
......@@ -329,6 +322,7 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
:uninstall-successful="applications.ingress.uninstallSuccessful"
:uninstall-failed="applications.ingress.uninstallFailed"
:disabled="!helmInstalled"
:updateable="false"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
>
<div slot="description">
......@@ -340,25 +334,10 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
}}
</p>
<template>
<div class="form-group">
<div class="form-check form-check-inline">
<input
v-model="applications.ingress.modsecurity_enabled"
:disabled="ingressInstalled"
type="checkbox"
autocomplete="off"
class="form-check-input"
/>
<label class="form-check-label label-bold" for="ingress-enable-modsecurity">
{{ s__('ClusterIntegration|Enable Web Application Firewall') }}
</label>
</div>
<p class="form-text text-muted">
<strong v-html="ingressModSecurityDescription"></strong>
</p>
</div>
</template>
<ingress-modsecurity-settings
:ingress="ingress"
:ingress-mod-security-help-path="ingressModSecurityHelpPath"
/>
<template v-if="ingressInstalled">
<div class="form-group">
......
<script>
import _ from 'lodash';
import { __ } from '../../locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
export default {
components: {
LoadingButton,
GlAlert,
GlSprintf,
GlLink,
},
props: {
ingress: {
type: Object,
required: true,
},
ingressModSecurityHelpPath: {
type: String,
required: false,
default: '',
},
},
computed: {
modSecurityEnabled: {
get() {
return this.ingress.modsecurity_enabled;
},
set(isEnabled) {
eventHub.$emit('setIngressModSecurityEnabled', {
id: INGRESS,
modSecurityEnabled: isEnabled,
});
},
},
ingressModSecurityDescription() {
return _.escape(this.ingressModSecurityHelpPath);
},
saving() {
return [UPDATING].includes(this.ingress.status);
},
saveButtonDisabled() {
return [UNINSTALLING, UPDATING].includes(this.ingress.status);
},
saveButtonLabel() {
return this.saving ? __('Saving') : __('Save changes');
},
ingressInstalled() {
return this.ingress.installed;
},
},
methods: {
updateApplication() {
eventHub.$emit('updateApplication', {
id: INGRESS,
params: { modsecurity_enabled: this.ingress.modsecurity_enabled },
});
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="ingress.updateFailed"
class="mb-3"
variant="danger"
:dismissible="false"
@dismiss="alert = null"
>
{{
s__('ClusterIntegration|Something went wrong while updating the Web Application Firewall.')
}}
</gl-alert>
<div class="form-group">
<div class="form-check form-check-inline">
<input
v-model="modSecurityEnabled"
type="checkbox"
autocomplete="off"
class="form-check-input"
/>
<label class="form-check-label label-bold" for="ingress-enable-modsecurity">
{{ s__('ClusterIntegration|Enable Web Application Firewall') }}
</label>
</div>
<p class="form-text text-muted">
<strong>
<gl-sprintf
:message="s__('ClusterIntegration|Learn more about %{linkStart}ModSecurity%{linkEnd}')"
>
<template #link="{ content }">
<gl-link :href="ingressModSecurityDescription" target="_blank"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
</strong>
</p>
<loading-button
v-if="ingressInstalled"
class="btn-success mt-1"
:loading="saving"
:disabled="saveButtonDisabled"
:label="saveButtonLabel"
@click="updateApplication"
/>
</div>
</div>
</template>
......@@ -54,6 +54,8 @@ export default class ClusterStore {
modsecurity_enabled: false,
externalIp: null,
externalHostname: null,
isEditingModSecurityEnabled: false,
updateFailed: false,
},
cert_manager: {
...applicationInitialState,
......@@ -208,8 +210,11 @@ export default class ClusterStore {
if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname;
this.state.applications.ingress.modsecurity_enabled =
serverAppEntry.modsecurity_enabled || this.state.applications.ingress.modsecurity_enabled;
if (!this.state.applications.ingress.isEditingModSecurityEnabled) {
this.state.applications.ingress.modsecurity_enabled =
serverAppEntry.modsecurity_enabled ||
this.state.applications.ingress.modsecurity_enabled;
}
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
......
<script>
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { GlLink, GlLoadingIcon } from '@gitlab/ui';
import getReadmeQuery from '../../queries/getReadme.query.graphql';
......@@ -30,6 +32,15 @@ export default {
loading: 0,
};
},
watch: {
readme(newVal) {
if (newVal) {
this.$nextTick(() => {
$(this.$refs.readme).renderGFM();
});
}
},
},
};
</script>
......@@ -45,7 +56,7 @@ export default {
</div>
<div class="blob-viewer">
<gl-loading-icon v-if="loading > 0" size="md" color="dark" class="my-4 mx-auto" />
<div v-else-if="readme" v-html="readme.html"></div>
<div v-else-if="readme" ref="readme" v-html="readme.html"></div>
</div>
</article>
</template>
......@@ -150,6 +150,12 @@
right: 8px;
}
.spinner {
position: absolute;
top: 9px;
right: 8px;
}
.ic-chevron-down {
position: absolute;
top: $gl-padding-8;
......
......@@ -97,7 +97,7 @@ class GroupsController < Groups::ApplicationController
end
def edit
@badge_api_endpoint = expose_url(api_v4_groups_badges_path(id: @group.id))
@badge_api_endpoint = expose_path(api_v4_groups_badges_path(id: @group.id))
end
def projects
......
......@@ -50,7 +50,7 @@ class ProjectsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def edit
@badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
@badge_api_endpoint = expose_path(api_v4_projects_badges_path(id: @project.id))
render_edit
end
......
---
title: Allow enabling/disabling modsecurity from UI
merge_request: 24747
author:
type: added
---
title: Migrated from .fa-spinner to .spinner in app/assets/javascripts/blob/template_selector.js
merge_request: 25045
author: Raihan Kabir (gitlab/rk4bir)
type: changed
......@@ -17,6 +17,14 @@ module Gitlab
buckets [100, 1000, 10000, 100000, 1000000, 10000000]
end
define_counter :gitlab_redis_diff_caching_hit do
docstring 'Redis diff caching hits'
end
define_counter :gitlab_redis_diff_caching_miss do
docstring 'Redis diff caching misses'
end
def initialize(diff_collection)
@diff_collection = diff_collection
end
......
......@@ -124,6 +124,8 @@ module Gitlab
self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end
record_metric_blob_size
# Retain the actual size before it is encoded
@loaded_size = @data.bytesize if @data
@loaded_all_data = @loaded_size == size
......@@ -202,6 +204,12 @@ module Gitlab
private
def record_metric_blob_size
return unless size
self.class.gitlab_blob_size.observe({}, size)
end
def has_lfs_version_key?
!empty? && text_in_repo? && data.start_with?("version https://git-lfs.github.com/spec")
end
......
......@@ -4310,7 +4310,7 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}ModSecurity%{endLink}"
msgid "ClusterIntegration|Learn more about %{linkStart}ModSecurity%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
......@@ -4595,6 +4595,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
msgstr ""
msgid "ClusterIntegration|Something went wrong while updating the Web Application Firewall."
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."
msgstr ""
......
......@@ -7,6 +7,7 @@ import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
import eventHub from '~/clusters/event_hub';
import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue';
import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
describe('Applications', () => {
let vm;
......@@ -156,6 +157,30 @@ describe('Applications', () => {
});
describe('Ingress application', () => {
describe('with nested component', () => {
const propsData = {
applications: {
...APPLICATIONS_MOCK_STATE,
ingress: {
title: 'Ingress',
status: 'installed',
},
},
};
let wrapper;
beforeEach(() => {
wrapper = shallowMount(Applications, { propsData });
});
afterEach(() => {
wrapper.destroy();
});
it('renders IngressModsecuritySettings', () => {
const modsecuritySettings = wrapper.find(IngressModsecuritySettings);
expect(modsecuritySettings.exists()).toBe(true);
});
});
describe('when installed', () => {
describe('with ip address', () => {
it('renders ip address with a clipboard button', () => {
......
import { shallowMount } from '@vue/test-utils';
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 { GlAlert } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
const { UPDATING } = APPLICATION_STATUS;
describe('IngressModsecuritySettings', () => {
let wrapper;
const defaultProps = {
modsecurity_enabled: false,
status: 'installable',
installed: false,
};
const createComponent = (props = defaultProps) => {
wrapper = shallowMount(IngressModsecuritySettings, {
propsData: {
ingress: {
...defaultProps,
...props,
},
},
});
};
const findSaveButton = () => wrapper.find(LoadingButton);
const findModSecurityCheckbox = () => wrapper.find('input').element;
describe('when ingress is installed', () => {
beforeEach(() => {
createComponent({ installed: true });
jest.spyOn(eventHub, '$emit');
});
it('renders save button', () => {
expect(findSaveButton().exists()).toBe(true);
expect(findModSecurityCheckbox().checked).toBe(false);
});
describe('and the save changes button is clicked', () => {
beforeEach(() => {
findSaveButton().vm.$emit('click');
});
it('triggers save event and pass current modsecurity value', () =>
wrapper.vm.$nextTick().then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: INGRESS,
params: { modsecurity_enabled: false },
});
}));
});
it('triggers set event to be propagated with the current modsecurity value', () => {
wrapper.setData({ modSecurityEnabled: true });
return wrapper.vm.$nextTick().then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('setIngressModSecurityEnabled', {
id: INGRESS,
modSecurityEnabled: true,
});
});
});
describe(`when ingress status is ${UPDATING}`, () => {
beforeEach(() => {
createComponent({ installed: true, status: UPDATING });
});
it('renders loading spinner in save button', () => {
expect(findSaveButton().props('loading')).toBe(true);
});
it('renders disabled save button', () => {
expect(findSaveButton().props('disabled')).toBe(true);
});
it('renders save button with "Saving" label', () => {
expect(findSaveButton().props('label')).toBe('Saving');
});
});
describe('when ingress fails to update', () => {
beforeEach(() => {
createComponent({ updateFailed: true });
});
it('displays a error message', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
});
});
});
describe('when ingress is not installed', () => {
beforeEach(() => {
createComponent();
});
it('does not render the save button', () => {
expect(findSaveButton().exists()).toBe(false);
expect(findModSecurityCheckbox().checked).toBe(false);
});
});
});
......@@ -81,8 +81,10 @@ describe('Clusters Store', () => {
externalIp: null,
externalHostname: null,
installed: false,
isEditingModSecurityEnabled: false,
installFailed: true,
uninstallable: false,
updateFailed: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
......
......@@ -149,5 +149,13 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
it 'defines :gitlab_redis_diff_caching_memory_usage_bytes histogram' do
expect(described_class).to respond_to(:gitlab_redis_diff_caching_memory_usage_bytes)
end
it 'defines :gitlab_redis_diff_caching_hit' do
expect(described_class).to respond_to(:gitlab_redis_diff_caching_hit)
end
it 'defines :gitlab_redis_diff_caching_miss' do
expect(described_class).to respond_to(:gitlab_redis_diff_caching_miss)
end
end
end
......@@ -12,10 +12,18 @@ describe Gitlab::Git::Blob, :seed_helper do
let(:blob) { Gitlab::Git::Blob.new(name: 'test') }
it 'handles nil data' do
expect(described_class).not_to receive(:gitlab_blob_size)
expect(blob.name).to eq('test')
expect(blob.size).to eq(nil)
expect(blob.loaded_size).to eq(nil)
end
it 'records blob size' do
expect(described_class).to receive(:gitlab_blob_size).and_call_original
Gitlab::Git::Blob.new(name: 'test', size: 1234)
end
end
shared_examples '.find' 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