Commit 48c5ecd0 authored by Zamir Martins Filho's avatar Zamir Martins Filho Committed by Paul Slaughter

Update fluentd ui controls in cluster app page

Add a new checkbox option which controls
if cilium logs will be considered by
Fluentd.

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29511
parent 4095185d
......@@ -14,6 +14,7 @@ import {
INGRESS_DOMAIN_SUFFIX,
CROSSPLANE,
KNATIVE,
FLUENTD,
} from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
......@@ -510,10 +511,10 @@ export default class Clusters {
});
}
setFluentdSettings({ id: appId, port, protocol, host }) {
this.store.updateAppProperty(appId, 'port', port);
this.store.updateAppProperty(appId, 'protocol', protocol);
this.store.updateAppProperty(appId, 'host', host);
setFluentdSettings(settings = {}) {
Object.entries(settings).forEach(([key, value]) => {
this.store.updateAppProperty(FLUENTD, key, value);
});
}
toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
......
......@@ -689,6 +689,8 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
host: applications.fluentd.host,
port: applications.fluentd.port,
protocol: applications.fluentd.protocol,
waf_log_enabled: applications.fluentd.wafLogEnabled,
cilium_log_enabled: applications.fluentd.ciliumLogEnabled,
}"
:uninstallable="applications.fluentd.uninstallable"
:uninstall-successful="applications.fluentd.uninstallSuccessful"
......@@ -701,12 +703,20 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
<p>
{{
s__(
`ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. Export Web Application Firewall logs to your favorite SIEM.`,
`ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed.`,
)
}}
</p>
<fluentd-output-settings :fluentd="applications.fluentd" />
<fluentd-output-settings
:port="applications.fluentd.port"
:protocol="applications.fluentd.protocol"
:host="applications.fluentd.host"
:waf-log-enabled="applications.fluentd.wafLogEnabled"
:cilium-log-enabled="applications.fluentd.ciliumLogEnabled"
:status="applications.fluentd.status"
:update-failed="applications.fluentd.updateFailed"
/>
</div>
</application-row>
</div>
......
<script>
import { __ } from '~/locale';
import { APPLICATION_STATUS, FLUENTD } from '~/clusters/constants';
import { GlAlert, GlDeprecatedButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import {
GlAlert,
GlDeprecatedButton,
GlDropdown,
GlDropdownItem,
GlFormCheckbox,
} from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
import { mapValues } from 'lodash';
const { UPDATING, UNINSTALLING, INSTALLING, INSTALLED, UPDATED } = APPLICATION_STATUS;
......@@ -12,24 +19,62 @@ export default {
GlDeprecatedButton,
GlDropdown,
GlDropdownItem,
GlFormCheckbox,
},
props: {
fluentd: {
type: Object,
required: true,
},
protocols: {
type: Array,
required: false,
default: () => ['TCP', 'UDP'],
},
status: {
type: String,
required: false,
default: '',
},
updateFailed: {
type: Boolean,
required: false,
},
protocol: {
type: String,
required: false,
default: () => __('Protocol'),
},
port: {
type: Number,
required: false,
default: 514,
},
host: {
type: String,
required: false,
default: '',
},
wafLogEnabled: {
type: Boolean,
required: false,
},
ciliumLogEnabled: {
type: Boolean,
required: false,
},
},
data: () => ({
currentServerSideSettings: {
host: null,
port: null,
protocol: null,
wafLogEnabled: null,
ciliumLogEnabled: null,
},
}),
computed: {
isSaving() {
return [UPDATING].includes(this.fluentd.status);
return [UPDATING].includes(this.status);
},
saveButtonDisabled() {
return [UNINSTALLING, UPDATING, INSTALLING].includes(this.fluentd.status);
return [UNINSTALLING, UPDATING, INSTALLING].includes(this.status);
},
saveButtonLabel() {
return this.isSaving ? __('Saving') : __('Save changes');
......@@ -41,32 +86,23 @@ export default {
* neither getting installed nor updated.
*/
showButtons() {
return (
this.isSaving ||
(this.fluentd.isEditingSettings && [INSTALLED, UPDATED].includes(this.fluentd.status))
);
return this.isSaving || (this.changedByUser && [INSTALLED, UPDATED].includes(this.status));
},
protocolName() {
if (this.fluentd.protocol !== null && this.fluentd.protocol !== undefined) {
return this.fluentd.protocol.toUpperCase();
if (this.protocol) {
return this.protocol.toUpperCase();
}
return __('Protocol');
},
fluentdPort: {
get() {
return this.fluentd.port;
},
set(port) {
this.setFluentSettings({ port });
},
},
fluentdHost: {
get() {
return this.fluentd.host;
},
set(host) {
this.setFluentSettings({ host });
},
changedByUser() {
return Object.entries(this.currentServerSideSettings).some(([key, value]) => {
return value !== null && value !== this[key];
});
},
},
watch: {
status() {
this.resetCurrentServerSideSettings();
},
},
methods: {
......@@ -74,38 +110,64 @@ export default {
eventHub.$emit('updateApplication', {
id: FLUENTD,
params: {
port: this.fluentd.port,
protocol: this.fluentd.protocol,
host: this.fluentd.host,
port: this.port,
protocol: this.protocol,
host: this.host,
waf_log_enabled: this.wafLogEnabled,
cilium_log_enabled: this.ciliumLogEnabled,
},
});
this.resetStatus();
},
resetCurrentServerSideSettings() {
this.currentServerSideSettings = mapValues(this.currentServerSideSettings, () => {
return null;
});
},
resetStatus() {
this.fluentd.isEditingSettings = false;
const newSettings = mapValues(this.currentServerSideSettings, (value, key) => {
return value === null ? this[key] : value;
});
eventHub.$emit('setFluentdSettings', {
...newSettings,
isEditingSettings: false,
});
},
selectProtocol(protocol) {
this.setFluentSettings({ protocol });
updateCurrentServerSideSettings(settings) {
Object.keys(settings).forEach(key => {
if (this.currentServerSideSettings[key] === null) {
this.currentServerSideSettings[key] = this[key];
}
});
},
setFluentSettings({ port, protocol, host }) {
this.fluentd.isEditingSettings = true;
const newPort = port !== undefined ? port : this.fluentd.port;
const newProtocol = protocol !== undefined ? protocol : this.fluentd.protocol;
const newHost = host !== undefined ? host : this.fluentd.host;
setFluentdSettings(settings) {
this.updateCurrentServerSideSettings(settings);
eventHub.$emit('setFluentdSettings', {
id: FLUENTD,
port: newPort,
protocol: newProtocol,
host: newHost,
...settings,
isEditingSettings: true,
});
},
selectProtocol(protocol) {
this.setFluentdSettings({ protocol });
},
hostChanged(host) {
this.setFluentdSettings({ host });
},
portChanged(port) {
this.setFluentdSettings({ port: Number(port) });
},
wafLogChanged(wafLogEnabled) {
this.setFluentdSettings({ wafLogEnabled });
},
ciliumLogChanged(ciliumLogEnabled) {
this.setFluentdSettings({ ciliumLogEnabled });
},
},
};
</script>
<template>
<div>
<gl-alert v-if="fluentd.updateFailed" class="mb-3" variant="danger" :dismissible="false">
<gl-alert v-if="updateFailed" class="mb-3" variant="danger" :dismissible="false">
{{
s__(
'ClusterIntegration|Something went wrong while trying to save your settings. Please try again.',
......@@ -117,13 +179,25 @@ export default {
<label for="fluentd-host">
<strong>{{ s__('ClusterIntegration|SIEM Hostname') }}</strong>
</label>
<input id="fluentd-host" v-model="fluentdHost" type="text" class="form-control" />
<input
id="fluentd-host"
:value="host"
type="text"
class="form-control"
@input="hostChanged($event.target.value)"
/>
</div>
<div class="form-group">
<label for="fluentd-port">
<strong>{{ s__('ClusterIntegration|SIEM Port') }}</strong>
</label>
<input id="fluentd-port" v-model="fluentdPort" type="text" class="form-control" />
<input
id="fluentd-port"
:value="port"
type="number"
class="form-control"
@input="portChanged($event.target.value)"
/>
</div>
<div class="form-group">
<label for="fluentd-protocol">
......@@ -133,12 +207,20 @@ export default {
<gl-dropdown-item
v-for="(value, index) in protocols"
:key="index"
@click="selectProtocol(value)"
@click="selectProtocol(value.toLowerCase())"
>
{{ value }}
</gl-dropdown-item>
</gl-dropdown>
</div>
<div class="form-group flex flex-wrap">
<gl-form-checkbox :checked="wafLogEnabled" @input="wafLogChanged">
<strong>{{ s__('ClusterIntegration|Send ModSecurity Logs') }}</strong>
</gl-form-checkbox>
<gl-form-checkbox :checked="ciliumLogEnabled" @input="ciliumLogChanged">
<strong>{{ s__('ClusterIntegration|Send Cilium Logs') }}</strong>
</gl-form-checkbox>
</div>
<div v-if="showButtons" class="mt-3">
<gl-deprecated-button
ref="saveBtn"
......
......@@ -110,6 +110,8 @@ export default class ClusterStore {
host: null,
port: null,
protocol: null,
wafLogEnabled: null,
ciliumLogEnabled: null,
isEditingSettings: false,
},
},
......@@ -267,6 +269,8 @@ export default class ClusterStore {
this.state.applications.fluentd.port = serverAppEntry.port;
this.state.applications.fluentd.host = serverAppEntry.host;
this.state.applications.fluentd.protocol = serverAppEntry.protocol;
this.state.applications.fluentd.wafLogEnabled = serverAppEntry.waf_log_enabled;
this.state.applications.fluentd.ciliumLogEnabled = serverAppEntry.cilium_log_enabled;
}
}
});
......
---
title: Add Cilium to Fluentd UI controls on the Cluster Application page
merge_request: 29511
author:
type: changed
......@@ -570,9 +570,10 @@ To enable Fluentd:
1. Provide the host domain name or URL in **SIEM Hostname**.
1. Provide the host port number in **SIEM Port**.
1. Select a **SIEM Protocol**.
1. Select at least one of the available logs (such as WAF or Cilium).
1. Click **Save changes**.
![Fluentd input fields](img/fluentd_v12_10.png)
![Fluentd input fields](img/fluentd_v13_0.png)
### Future apps
......
......@@ -4542,7 +4542,7 @@ msgstr ""
msgid "ClusterIntegration|Fluentd"
msgstr ""
msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. Export Web Application Firewall logs to your favorite SIEM."
msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
msgstr ""
msgid "ClusterIntegration|GitLab Integration"
......@@ -4956,6 +4956,12 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
msgid "ClusterIntegration|Send Cilium Logs"
msgstr ""
msgid "ClusterIntegration|Send ModSecurity Logs"
msgstr ""
msgid "ClusterIntegration|Service Token"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import FluentdOutputSettings from '~/clusters/components/fluentd_output_settings.vue';
import { APPLICATION_STATUS, FLUENTD } from '~/clusters/constants';
import { GlAlert, GlDropdown } from '@gitlab/ui';
import { GlAlert, GlDropdown, GlFormCheckbox } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
const { UPDATING } = APPLICATION_STATUS;
......@@ -9,34 +9,58 @@ const { UPDATING } = APPLICATION_STATUS;
describe('FluentdOutputSettings', () => {
let wrapper;
const defaultProps = {
status: 'installable',
installed: false,
updateAvailable: false,
const defaultSettings = {
protocol: 'tcp',
host: '127.0.0.1',
port: 514,
isEditingSettings: false,
wafLogEnabled: true,
ciliumLogEnabled: false,
};
const defaultProps = {
status: 'installable',
updateFailed: false,
...defaultSettings,
};
const createComponent = (props = {}) => {
wrapper = shallowMount(FluentdOutputSettings, {
propsData: {
fluentd: {
...defaultProps,
...props,
},
...defaultProps,
...props,
},
});
};
const updateComponentPropsFromEvent = () => {
const { isEditingSettings, ...props } = eventHub.$emit.mock.calls[0][1];
wrapper.setProps(props);
};
const findSaveButton = () => wrapper.find({ ref: 'saveBtn' });
const findCancelButton = () => wrapper.find({ ref: 'cancelBtn' });
const findProtocolDropdown = () => wrapper.find(GlDropdown);
const findCheckbox = name =>
wrapper.findAll(GlFormCheckbox).wrappers.find(x => x.text() === name);
const findHost = () => wrapper.find('#fluentd-host');
const findPort = () => wrapper.find('#fluentd-port');
const changeCheckbox = checkbox => {
const currentValue = checkbox.attributes('checked')?.toString() === 'true';
checkbox.vm.$emit('input', !currentValue);
};
const changeInput = ({ element }, val) => {
element.value = val;
element.dispatchEvent(new Event('input'));
};
const changePort = val => changeInput(findPort(), val);
const changeHost = val => changeInput(findHost(), val);
const changeProtocol = idx => findProtocolDropdown().vm.$children[idx].$emit('click');
const toApplicationSettings = ({ wafLogEnabled, ciliumLogEnabled, ...settings }) => ({
...settings,
waf_log_enabled: wafLogEnabled,
cilium_log_enabled: ciliumLogEnabled,
});
describe('when fluentd is installed', () => {
beforeEach(() => {
createComponent({ installed: true, status: 'installed' });
createComponent({ status: 'installed' });
jest.spyOn(eventHub, '$emit');
});
......@@ -45,73 +69,77 @@ describe('FluentdOutputSettings', () => {
expect(findCancelButton().exists()).toBe(false);
});
describe('with protocol dropdown changed by the user', () => {
describe.each`
desc | changeFn | key | value
${'when protocol dropdown is triggered'} | ${() => changeProtocol(1)} | ${'protocol'} | ${'udp'}
${'when host is changed'} | ${() => changeHost('test-host')} | ${'host'} | ${'test-host'}
${'when port is changed'} | ${() => changePort(123)} | ${'port'} | ${123}
${'when wafLogEnabled changes'} | ${() => changeCheckbox(findCheckbox('Send ModSecurity Logs'))} | ${'wafLogEnabled'} | ${!defaultSettings.wafLogEnabled}
${'when ciliumLogEnabled changes'} | ${() => changeCheckbox(findCheckbox('Send Cilium Logs'))} | ${'ciliumLogEnabled'} | ${!defaultSettings.ciliumLogEnabled}
`('$desc', ({ changeFn, key, value }) => {
beforeEach(() => {
findProtocolDropdown().vm.$children[1].$emit('click');
wrapper.setProps({
fluentd: {
...defaultProps,
installed: true,
status: 'installed',
protocol: 'udp',
isEditingSettings: true,
},
});
});
it('renders save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(true);
expect(findCancelButton().exists()).toBe(true);
});
it('enables related toggle and buttons', () => {
expect(findSaveButton().attributes().disabled).toBeUndefined();
expect(findCancelButton().attributes().disabled).toBeUndefined();
changeFn();
});
it('triggers set event to be propagated with the current value', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('setFluentdSettings', {
id: FLUENTD,
host: '127.0.0.1',
port: 514,
protocol: 'UDP',
[key]: value,
isEditingSettings: true,
});
});
describe('and the save changes button is clicked', () => {
describe('when value is updated from store', () => {
beforeEach(() => {
findSaveButton().vm.$emit('click');
updateComponentPropsFromEvent();
});
it('enables save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(true);
expect(findSaveButton().attributes().disabled).toBeUndefined();
expect(findCancelButton().exists()).toBe(true);
expect(findCancelButton().attributes().disabled).toBeUndefined();
});
it('triggers save event and pass current values', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: FLUENTD,
params: {
host: '127.0.0.1',
port: 514,
protocol: 'udp',
},
describe('and the save changes button is clicked', () => {
beforeEach(() => {
eventHub.$emit.mockClear();
findSaveButton().vm.$emit('click');
});
it('triggers save event and pass current values', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: FLUENTD,
params: toApplicationSettings({
...defaultSettings,
[key]: value,
}),
});
});
});
});
describe('and the cancel button is clicked', () => {
beforeEach(() => {
findCancelButton().vm.$emit('click');
wrapper.setProps({
fluentd: {
...defaultProps,
installed: true,
status: 'installed',
protocol: 'udp',
describe('and the cancel button is clicked', () => {
beforeEach(() => {
eventHub.$emit.mockClear();
findCancelButton().vm.$emit('click');
});
it('triggers reset event', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('setFluentdSettings', {
...defaultSettings,
isEditingSettings: false,
},
});
});
});
it('triggers reset event and hides both cancel and save changes button', () => {
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
describe('when value is updated from store', () => {
beforeEach(() => {
updateComponentPropsFromEvent();
});
it('does not render save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
});
});
});
});
});
......
......@@ -127,6 +127,7 @@ describe('Clusters Store', () => {
statusReason: null,
requestReason: null,
port: null,
ciliumLogEnabled: null,
host: null,
protocol: null,
installed: false,
......@@ -136,6 +137,7 @@ describe('Clusters Store', () => {
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
wafLogEnabled: null,
},
jupyter: {
title: 'JupyterHub',
......
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