Commit 32793249 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '238484-add-policy-alert-picker' into 'master'

Add policy alert picker

See merge request gitlab-org/gitlab!50305
parents 29056814 7a415b19
...@@ -3,6 +3,7 @@ import { mapActions } from 'vuex'; ...@@ -3,6 +3,7 @@ import { mapActions } from 'vuex';
import { GlAlert, GlEmptyState, GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui'; import { GlAlert, GlEmptyState, GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Alerts from './alerts/alerts.vue'; import Alerts from './alerts/alerts.vue';
import ThreatMonitoringFilters from './threat_monitoring_filters.vue'; import ThreatMonitoringFilters from './threat_monitoring_filters.vue';
import ThreatMonitoringSection from './threat_monitoring_section.vue'; import ThreatMonitoringSection from './threat_monitoring_section.vue';
...@@ -24,6 +25,7 @@ export default { ...@@ -24,6 +25,7 @@ export default {
NetworkPolicyList, NetworkPolicyList,
}, },
inject: ['documentationPath'], inject: ['documentationPath'],
mixins: [glFeatureFlagsMixin()],
props: { props: {
defaultEnvironmentId: { defaultEnvironmentId: {
type: Number, type: Number,
...@@ -75,7 +77,7 @@ export default { ...@@ -75,7 +77,7 @@ export default {
}, },
computed: { computed: {
showAlertsTab() { showAlertsTab() {
return gon.features.threatMonitoringAlerts; return this.glFeatures.threatMonitoringAlerts;
}, },
}, },
created() { created() {
......
...@@ -113,7 +113,7 @@ function parseRule(item, direction) { ...@@ -113,7 +113,7 @@ function parseRule(item, direction) {
*/ */
export default function fromYaml(manifest) { export default function fromYaml(manifest) {
const { description, metadata, spec } = safeLoad(manifest, { json: true }); const { description, metadata, spec } = safeLoad(manifest, { json: true });
const { name, resourceVersion } = metadata; const { name, resourceVersion, annotations } = metadata;
const { endpointSelector = {}, ingress = [], egress = [] } = spec; const { endpointSelector = {}, ingress = [], egress = [] } = spec;
const matchLabels = endpointSelector.matchLabels || {}; const matchLabels = endpointSelector.matchLabels || {};
...@@ -134,6 +134,7 @@ export default function fromYaml(manifest) { ...@@ -134,6 +134,7 @@ export default function fromYaml(manifest) {
name, name,
resourceVersion, resourceVersion,
description, description,
annotations,
isEnabled: !Object.keys(matchLabels).includes(DisabledByLabel), isEnabled: !Object.keys(matchLabels).includes(DisabledByLabel),
endpointMatchMode: endpointLabels.length > 0 ? EndpointMatchModeLabel : EndpointMatchModeAny, endpointMatchMode: endpointLabels.length > 0 ? EndpointMatchModeLabel : EndpointMatchModeAny,
endpointLabels: endpointLabels.join(' '), endpointLabels: endpointLabels.join(' '),
......
...@@ -33,8 +33,11 @@ function spec({ rules, isEnabled, endpointMatchMode, endpointLabels }) { ...@@ -33,8 +33,11 @@ function spec({ rules, isEnabled, endpointMatchMode, endpointLabels }) {
Return yaml representation of a policy. Return yaml representation of a policy.
*/ */
export default function toYaml(policy) { export default function toYaml(policy) {
const { name, resourceVersion, description } = policy; const { annotations, name, resourceVersion, description } = policy;
const metadata = { name }; const metadata = { name };
if (annotations) {
metadata.annotations = annotations;
}
if (resourceVersion) { if (resourceVersion) {
metadata.resourceVersion = resourceVersion; metadata.resourceVersion = resourceVersion;
} }
......
<script>
import { GlAlert, GlButton, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
i18n: {
ACTION: s__(
'NetworkPolicies|%{labelStart}And%{labelEnd} %{spanStart}send an Alert to GitLab.%{spanEnd}',
),
BUTTON_LABEL: s__('NetworkPolicies|+ Add alert'),
HIGH_VOLUME_WARNING: s__(
`NetworkPolicies|Alerts are intended to be selectively used for a limited number of events that are potentially concerning and warrant a manual review. Alerts should not be used as a substitute for a SIEM or a logging tool. High volume alerts are likely to be dropped so as to preserve the stability of GitLab's integration with Kubernetes.`,
),
},
components: {
GlAlert,
GlButton,
GlSprintf,
},
props: {
policyAlert: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div>
<gl-alert v-if="policyAlert" variant="warning" :dismissible="false" class="gl-mt-5">
{{ $options.i18n.HIGH_VOLUME_WARNING }}
</gl-alert>
<div
class="gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base gl-p-5"
:class="{ 'gl-mt-5': !policyAlert }"
>
<gl-button
v-if="!policyAlert"
variant="link"
category="primary"
data-testid="add-alert"
@click="$emit('update-alert', !policyAlert)"
>
{{ $options.i18n.BUTTON_LABEL }}
</gl-button>
<div
v-else
class="gl-w-full gl-display-flex gl-justify-content-space-between gl-align-items-center"
>
<span>
<gl-sprintf :message="$options.i18n.ACTION">
<template #label="{ content }">
<label for="actionType" class="text-uppercase gl-font-lg gl-mr-4 gl-mb-0">{{
content
}}</label>
</template>
<template #span="{ content }">
<span>{{ content }}</span>
</template>
</gl-sprintf>
</span>
<gl-button
data-testid="remove-alert"
icon="remove"
category="tertiary"
@click="$emit('update-alert', !policyAlert)"
/>
</div>
</div>
</div>
</template>
...@@ -14,11 +14,13 @@ import { ...@@ -14,11 +14,13 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import EnvironmentPicker from '../environment_picker.vue'; import EnvironmentPicker from '../environment_picker.vue';
import NetworkPolicyEditor from '../network_policy_editor.vue'; import NetworkPolicyEditor from '../network_policy_editor.vue';
import PolicyRuleBuilder from './policy_rule_builder.vue'; import PolicyRuleBuilder from './policy_rule_builder.vue';
import PolicyPreview from './policy_preview.vue'; import PolicyPreview from './policy_preview.vue';
import PolicyActionPicker from './policy_action_picker.vue'; import PolicyActionPicker from './policy_action_picker.vue';
import PolicyAlertPicker from './policy_alert_picker.vue';
import DimDisableContainer from './dim_disable_container.vue'; import DimDisableContainer from './dim_disable_container.vue';
import { import {
EditorModeRule, EditorModeRule,
...@@ -47,9 +49,11 @@ export default { ...@@ -47,9 +49,11 @@ export default {
PolicyRuleBuilder, PolicyRuleBuilder,
PolicyPreview, PolicyPreview,
PolicyActionPicker, PolicyActionPicker,
PolicyAlertPicker,
DimDisableContainer, DimDisableContainer,
}, },
directives: { GlModal: GlModalDirective }, directives: { GlModal: GlModalDirective },
mixins: [glFeatureFlagsMixin()],
props: { props: {
threatMonitoringPath: { threatMonitoringPath: {
type: String, type: String,
...@@ -71,6 +75,7 @@ export default { ...@@ -71,6 +75,7 @@ export default {
endpointMatchMode: EndpointMatchModeAny, endpointMatchMode: EndpointMatchModeAny,
endpointLabels: '', endpointLabels: '',
rules: [], rules: [],
annotations: '',
}; };
return { return {
...@@ -84,6 +89,9 @@ export default { ...@@ -84,6 +89,9 @@ export default {
humanizedPolicy() { humanizedPolicy() {
return humanizeNetworkPolicy(this.policy); return humanizeNetworkPolicy(this.policy);
}, },
policyAlert() {
return Boolean(this.policy.annotations);
},
policyYaml() { policyYaml() {
return toYaml(this.policy); return toYaml(this.policy);
}, },
...@@ -111,6 +119,9 @@ export default { ...@@ -111,6 +119,9 @@ export default {
? s__('NetworkPolicies|Save changes') ? s__('NetworkPolicies|Save changes')
: s__('NetworkPolicies|Create policy'); : s__('NetworkPolicies|Create policy');
}, },
showAlertsPicker() {
return this.glFeatures.threatMonitoringAlerts;
},
deleteModalTitle() { deleteModalTitle() {
return sprintf(s__('NetworkPolicies|Delete policy: %{policy}'), { policy: this.policy.name }); return sprintf(s__('NetworkPolicies|Delete policy: %{policy}'), { policy: this.policy.name });
}, },
...@@ -124,6 +135,9 @@ export default { ...@@ -124,6 +135,9 @@ export default {
addRule() { addRule() {
this.policy.rules.push(buildRule(RuleTypeEndpoint)); this.policy.rules.push(buildRule(RuleTypeEndpoint));
}, },
handleAlertUpdate(includeAlert) {
this.policy.annotations = includeAlert ? { 'app.gitlab.com/alert': 'true' } : '';
},
updateEndpointMatchMode(mode) { updateEndpointMatchMode(mode) {
this.policy.endpointMatchMode = mode; this.policy.endpointMatchMode = mode;
}, },
...@@ -308,6 +322,11 @@ export default { ...@@ -308,6 +322,11 @@ export default {
</template> </template>
<policy-action-picker /> <policy-action-picker />
<policy-alert-picker
v-if="showAlertsPicker"
:policy-alert="policyAlert"
@update-alert="handleAlertUpdate"
/>
</dim-disable-container> </dim-disable-container>
</div> </div>
<div class="col-sm-12 col-md-6 col-lg-5 col-xl-4"> <div class="col-sm-12 col-md-6 col-lg-5 col-xl-4">
......
...@@ -234,7 +234,7 @@ export default { ...@@ -234,7 +234,7 @@ export default {
<gl-button <gl-button
icon="remove" icon="remove"
size="small" category="tertiary"
class="gl-absolute gl-top-3 gl-right-3" class="gl-absolute gl-top-3 gl-right-3"
data-testid="remove-rule" data-testid="remove-rule"
@click="$emit('remove')" @click="$emit('remove')"
......
...@@ -24,9 +24,8 @@ const userCalloutsPath = `${TEST_HOST}/user_callouts`; ...@@ -24,9 +24,8 @@ const userCalloutsPath = `${TEST_HOST}/user_callouts`;
describe('ThreatMonitoringApp component', () => { describe('ThreatMonitoringApp component', () => {
let store; let store;
let wrapper; let wrapper;
window.gon = { features: {} };
const factory = ({ propsData, state, options } = {}) => { const factory = ({ propsData, provide = {}, state, options } = {}) => {
store = createStore(); store = createStore();
Object.assign(store.state.threatMonitoring, { Object.assign(store.state.threatMonitoring, {
environmentsEndpoint, environmentsEndpoint,
...@@ -52,6 +51,8 @@ describe('ThreatMonitoringApp component', () => { ...@@ -52,6 +51,8 @@ describe('ThreatMonitoringApp component', () => {
}, },
provide: { provide: {
documentationPath, documentationPath,
glFeatures: { threatMonitoringAlerts: false },
...provide,
}, },
store, store,
...options, ...options,
...@@ -68,7 +69,6 @@ describe('ThreatMonitoringApp component', () => { ...@@ -68,7 +69,6 @@ describe('ThreatMonitoringApp component', () => {
const findAlertTab = () => wrapper.find('[data-testid="threat-monitoring-alerts-tab"]'); const findAlertTab = () => wrapper.find('[data-testid="threat-monitoring-alerts-tab"]');
afterEach(() => { afterEach(() => {
window.gon.features = {};
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
}); });
...@@ -176,8 +176,7 @@ describe('ThreatMonitoringApp component', () => { ...@@ -176,8 +176,7 @@ describe('ThreatMonitoringApp component', () => {
describe('alerts tab', () => { describe('alerts tab', () => {
beforeEach(() => { beforeEach(() => {
window.gon.features.threatMonitoringAlerts = true; factory({ provide: { glFeatures: { threatMonitoringAlerts: true } } });
factory({});
}); });
it('shows the alerts tab', () => { it('shows the alerts tab', () => {
expect(findAlertTab().exists()).toBe(true); expect(findAlertTab().exists()).toBe(true);
......
...@@ -178,6 +178,8 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = ` ...@@ -178,6 +178,8 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = `
> >
<policy-action-picker-stub /> <policy-action-picker-stub />
<!---->
</dim-disable-container-stub> </dim-disable-container-stub>
</div> </div>
......
...@@ -288,4 +288,24 @@ spec: ...@@ -288,4 +288,24 @@ spec:
}); });
}); });
}); });
describe('when annotations is not empty', () => {
it('returns policy object', () => {
const manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: test-policy
annotations:
app.gitlab.com/alert: 'true'
spec:
endpointSelector:
matchLabels:
{}
`;
expect(fromYaml(manifest)).toMatchObject({
annotations: { 'app.gitlab.com/alert': 'true' },
});
});
});
}); });
...@@ -21,6 +21,26 @@ spec: ...@@ -21,6 +21,26 @@ spec:
`); `);
}); });
describe('when annotations is not empty', () => {
beforeEach(() => {
policy.annotations = { 'test annotation': true };
});
it('returns yaml representation', () => {
expect(toYaml(policy)).toEqual(`apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: test-policy
annotations:
test annotation: true
spec:
endpointSelector:
matchLabels:
network-policy.gitlab.com/disabled_by: gitlab
`);
});
});
describe('when description is not empty', () => { describe('when description is not empty', () => {
beforeEach(() => { beforeEach(() => {
policy.description = 'test description'; policy.description = 'test description';
......
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf } from '@gitlab/ui';
import PolicyAlertPicker from 'ee/threat_monitoring/components/policy_editor/policy_alert_picker.vue';
describe('PolicyAlertPicker component', () => {
let wrapper;
const defaultProps = { policyAlert: false };
const findAddAlertButton = () => wrapper.find("[data-testid='add-alert']");
const findGlAlert = () => wrapper.find(GlAlert);
const findGlSprintf = () => wrapper.find(GlSprintf);
const findRemoveAlertButton = () => wrapper.find("[data-testid='remove-alert']");
const createWrapper = ({ propsData = defaultProps } = {}) => {
wrapper = shallowMount(PolicyAlertPicker, {
propsData: {
...propsData,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('default state', () => {
beforeEach(() => {
createWrapper();
});
it('does render the add alert button', () => {
expect(findAddAlertButton().exists()).toBe(true);
});
it('does not render the high volume warning', () => {
expect(findGlAlert().exists()).toBe(false);
});
it('does not render the alert message', () => {
expect(findGlSprintf().exists()).toBe(false);
});
it('does not render the remove alert button', () => {
expect(findRemoveAlertButton().exists()).toBe(false);
});
it('does emit an event to add the alert', () => {
findAddAlertButton().vm.$emit('click');
expect(wrapper.emitted('update-alert')).toEqual([[true]]);
});
});
describe('alert enabled', () => {
beforeEach(() => {
createWrapper({ propsData: { policyAlert: true } });
});
it('does not render the add alert button', () => {
expect(findAddAlertButton().exists()).toBe(false);
});
it('does render the high volume warning', () => {
expect(findGlAlert().exists()).toBe(true);
});
it('does render the alert message', () => {
expect(findGlSprintf().exists()).toBe(true);
});
it('does render the remove alert button', () => {
expect(findRemoveAlertButton().exists()).toBe(true);
});
it('does emit an event to remove the alert', () => {
findRemoveAlertButton().vm.$emit('click');
expect(wrapper.emitted('update-alert')).toEqual([[false]]);
});
});
});
...@@ -14,6 +14,7 @@ import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml'; ...@@ -14,6 +14,7 @@ import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml';
import PolicyEditorApp from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue'; import PolicyEditorApp from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue'; import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/policy_rule_builder.vue'; import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/policy_rule_builder.vue';
import PolicyAlertPicker from 'ee/threat_monitoring/components/policy_editor/policy_alert_picker.vue';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
...@@ -23,7 +24,7 @@ describe('PolicyEditorApp component', () => { ...@@ -23,7 +24,7 @@ describe('PolicyEditorApp component', () => {
let store; let store;
let wrapper; let wrapper;
const factory = ({ propsData, state, data } = {}) => { const factory = ({ propsData, provide = {}, state, data } = {}) => {
store = createStore(); store = createStore();
Object.assign(store.state.threatMonitoring, { Object.assign(store.state.threatMonitoring, {
...state, ...state,
...@@ -39,6 +40,10 @@ describe('PolicyEditorApp component', () => { ...@@ -39,6 +40,10 @@ describe('PolicyEditorApp component', () => {
threatMonitoringPath: '/threat-monitoring', threatMonitoringPath: '/threat-monitoring',
...propsData, ...propsData,
}, },
provide: {
glFeatures: { threatMonitoringAlerts: false },
...provide,
},
store, store,
data, data,
}); });
...@@ -50,17 +55,28 @@ describe('PolicyEditorApp component', () => { ...@@ -50,17 +55,28 @@ describe('PolicyEditorApp component', () => {
const findAddRuleButton = () => wrapper.find('[data-testid="add-rule"]'); const findAddRuleButton = () => wrapper.find('[data-testid="add-rule"]');
const findYAMLParsingAlert = () => wrapper.find('[data-testid="parsing-alert"]'); const findYAMLParsingAlert = () => wrapper.find('[data-testid="parsing-alert"]');
const findNetworkPolicyEditor = () => wrapper.find(NetworkPolicyEditor); const findNetworkPolicyEditor = () => wrapper.find(NetworkPolicyEditor);
const findPolicyAlertPicker = () => wrapper.find(PolicyAlertPicker);
const findPolicyName = () => wrapper.find("[id='policyName']"); const findPolicyName = () => wrapper.find("[id='policyName']");
const findSavePolicy = () => wrapper.find("[data-testid='save-policy']"); const findSavePolicy = () => wrapper.find("[data-testid='save-policy']");
const findDeletePolicy = () => wrapper.find("[data-testid='delete-policy']"); const findDeletePolicy = () => wrapper.find("[data-testid='delete-policy']");
const findEditorModeToggle = () => wrapper.find("[data-testid='editor-mode']"); const findEditorModeToggle = () => wrapper.find("[data-testid='editor-mode']");
const modifyPolicyAlert = async ({ isAlertEnabled }) => {
const policyAlertPicker = findPolicyAlertPicker();
policyAlertPicker.vm.$emit('update-alert', isAlertEnabled);
await wrapper.vm.$nextTick();
expect(policyAlertPicker.props('policyAlert')).toBe(isAlertEnabled);
findSavePolicy().vm.$emit('click');
await wrapper.vm.$nextTick();
};
beforeEach(() => { beforeEach(() => {
factory(); factory();
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('renders the policy editor layout', () => { it('renders the policy editor layout', () => {
...@@ -79,6 +95,10 @@ describe('PolicyEditorApp component', () => { ...@@ -79,6 +95,10 @@ describe('PolicyEditorApp component', () => {
expect(findDeletePolicy().exists()).toBe(false); expect(findDeletePolicy().exists()).toBe(false);
}); });
it('does not render the policy alert picker', () => {
expect(findPolicyAlertPicker().exists()).toBe(false);
});
describe('given .yaml editor mode is enabled', () => { describe('given .yaml editor mode is enabled', () => {
beforeEach(() => { beforeEach(() => {
factory({ factory({
...@@ -339,4 +359,34 @@ spec: ...@@ -339,4 +359,34 @@ spec:
expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring'); expect(redirectTo).toHaveBeenCalledWith('/threat-monitoring');
}); });
}); });
describe('add alert picker', () => {
beforeEach(() => {
factory({ provide: { glFeatures: { threatMonitoringAlerts: true } } });
});
it('does render the policy alert picker', () => {
expect(findPolicyAlertPicker().exists()).toBe(true);
});
it('adds a policy annotation on alert addition', async () => {
await modifyPolicyAlert({ isAlertEnabled: true });
expect(store.dispatch).toHaveBeenLastCalledWith('networkPolicies/createPolicy', {
environmentId: -1,
policy: {
manifest: expect.stringContaining("app.gitlab.com/alert: 'true'"),
},
});
});
it('removes a policy annotation on alert removal', async () => {
await modifyPolicyAlert({ isAlertEnabled: false });
expect(store.dispatch).toHaveBeenLastCalledWith('networkPolicies/createPolicy', {
environmentId: -1,
policy: {
manifest: expect.not.stringContaining("app.gitlab.com/alert: 'true'"),
},
});
});
});
}); });
...@@ -18507,6 +18507,9 @@ msgstr "" ...@@ -18507,6 +18507,9 @@ msgstr ""
msgid "NetworkPolicies|%{ifLabelStart}if%{ifLabelEnd} %{ruleType} %{isLabelStart}is%{isLabelEnd} %{ruleDirection} %{ruleSelector} %{directionLabelStart}and is outbound to a%{directionLabelEnd} %{rule} %{portsLabelStart}on%{portsLabelEnd} %{ports}" msgid "NetworkPolicies|%{ifLabelStart}if%{ifLabelEnd} %{ruleType} %{isLabelStart}is%{isLabelEnd} %{ruleDirection} %{ruleSelector} %{directionLabelStart}and is outbound to a%{directionLabelEnd} %{rule} %{portsLabelStart}on%{portsLabelEnd} %{ports}"
msgstr "" msgstr ""
msgid "NetworkPolicies|%{labelStart}And%{labelEnd} %{spanStart}send an Alert to GitLab.%{spanEnd}"
msgstr ""
msgid "NetworkPolicies|%{labelStart}Then%{labelEnd} %{action} %{spanStart}the network traffic.%{spanEnd}" msgid "NetworkPolicies|%{labelStart}Then%{labelEnd} %{action} %{spanStart}the network traffic.%{spanEnd}"
msgstr "" msgstr ""
...@@ -18519,6 +18522,9 @@ msgstr "" ...@@ -18519,6 +18522,9 @@ msgstr ""
msgid "NetworkPolicies|%{strongOpen}any%{strongClose} port" msgid "NetworkPolicies|%{strongOpen}any%{strongClose} port"
msgstr "" msgstr ""
msgid "NetworkPolicies|+ Add alert"
msgstr ""
msgid "NetworkPolicies|.yaml" msgid "NetworkPolicies|.yaml"
msgstr "" msgstr ""
...@@ -18528,6 +18534,9 @@ msgstr "" ...@@ -18528,6 +18534,9 @@ msgstr ""
msgid "NetworkPolicies|Actions" msgid "NetworkPolicies|Actions"
msgstr "" msgstr ""
msgid "NetworkPolicies|Alerts are intended to be selectively used for a limited number of events that are potentially concerning and warrant a manual review. Alerts should not be used as a substitute for a SIEM or a logging tool. High volume alerts are likely to be dropped so as to preserve the stability of GitLab's integration with Kubernetes."
msgstr ""
msgid "NetworkPolicies|All selected" msgid "NetworkPolicies|All selected"
msgstr "" msgstr ""
......
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