Skip to content

Commit

Permalink
Add header and footer report customization (#4505)
Browse files Browse the repository at this point in the history
* feat(settings): centralize the plugin settings

Create the plugin setting schema
Define the current plugin settings
Remove refactored code

* feat(settings): add setting services and replaced the references to constants

* feat(settings): refactor the content of the default configuration file

Use dynamically the definition of the plugin settings

* feat(inputs): create new inputs components

Add new hooks to manage when a input value or form has changed
Add new inputs components

* feat(configuration): refactor the form of Settings/Configuration

Refactor Header, BottomBar, Configuration components
Remove deprecated files

* feat(settings): support updating multiple setting at the same time

Changed the endpoint that updating the plugin setting to support
  multiple settings at the same time
Refactor the getConfiguration service. Split the logic to:
  - Read the file and transform to JSON
  - Obfuscate the password key of the host configuration

* feat: add validation to the plugin settings

Create services to validate
Add the validation to the plugin settings

* feat(validation): add validation to the `PUT /utils/configuration`
endpoint

* feat(validation): add validation to the configuration form in
`Settings/Configuration`

* feat(validatio): remove no used import

* clean: remove not used code

* Add report header-footer configuration

* fix: fixed category name in `Settings/Configuration`

* fix(settings): Fix accessing to `validate` of undefined error

* fix(settings): fixed error due to missing service

* Fix custom logo ratio in PDF sheet

* fix(settings): refactor the form and inputs of `Settings/Configuration` to control the global state of the form

* fix: add value transformation for the form inputs and output of fields changed

* fix: Fixed some settings validation

* fix(settings): fixed validation of literals

* fix(settings): removed unused import

* fix(settings): renamed properties related to transform the value of the input

* feat(settings): add description to the plugin setting definition properties

* fix(settings): fix getConfiguration service when the configuration file has no `hosts` entry

* fix(settings): Fixed error when do changes of the `useForm` hook an rename methods of this

* tests(settings): add test related to the plugin settings and its configuration from the UI

* feat(settings): rename plugin setting options of type select to match its type

* feat(settings): add plugin settings services and enhance the description of the plugin settings in default configuration file and UI

* tests(input-form): update tests of InputForm component

* test(configuration-file): add tests of the default configuration file

* feat(settings): remove `extensions.mitre` plugin setting

* test(settings): add test to validate the plugin setting when updating it through PUT /utils/configuration

fix some plugin settings validation

* feat(settings): add documentation to some setting services and test some of them

* fix: fixed documentation of setting service

* doc(settings): move the documentation of the plugin setting properties

* fix(settings): rename some plugin setting properties because of request changes

- Rename plugin setting properties:
  - `default` to `defaultValue`
  - `defaultHidden` to `defaultValueIfNotSet`
  - `configurableFile` to `isConfigurableFromFile`
  - configurableUI` to `isConfigurableFromUI`
  - `requireHealthCheck` to `requiresRunningHealthCheck`
  - `requireReload` to `requiresReloadingBrowserTab`
  - `requireRestart` to `requiresRestartingPluginPlatform`
- Fix tests

* tests: fix tests of InputForm component

* fix: response properties when saving the configuration

* fix(settings): fix validation plugin settings value in the UI

* fix(settings): fix `customization.reports.header` and `customization.reports.footer` setting properties

* fix(settings): fix validation of numbers

* fix(settings): fix validation of numbers

* test(settings): Add tests related to validation for the `useForm` hook and the `InputForm` component

* fix(settings): fix displaying toast to run the healthcheck when saving the configuration

* test(settings): add tests for the `customization.reports.footer` and `customization.reports.header` settings

* Added category sorting + description + docs link

* Added settings sorting within their category

* Fixed constant types definition

* Checks if localCompare exists validation

* fix(settings): fixed plugin setting description doesn't display the minimum number value when it is falsy (0)

* fix(settings): fix setting type of `wazuh.monitoring.replicas` and limit the valid number for the number input

* feat(settins): add plugin settings category description

* fix(settings): fix a problem comparing the initial and current value for the plugin settings of the `number` type

* fix(settings): fix wrong conflict resolution

* fix(settings): fix typo in setting description

* Add set custom header footer unit test

* feat(settings): enhance the validation of plugin settings related to indices or index patterns taking in account the supported characters

* feat(settings): add validation of setting values in the inputs

* Added unit test to verify the PDF report integrity

* fix(tests): format tables of the tests

* fix: remove unnecessary import

* test(endpoints): add test to GET /reports endpoint

* Improved report unit test with more cases

* Fix small typo

* fix(settings): fix a typo in a toast related to modify the plugin settings from UI

* Changed Custom Branding documentation link

* Merge centralize plugin settings PR

* Fix white-labeling documentation link

* Code format

* Delete unused imports

* fix(settings): fix a problem with the useForm hook

* fix(settings): refactor the settings validation function to a class and rename the file

* feat(settings): add check for integer numbers and adapt the affected settings

* test: fix tests

* Set textArea size

* Add max character length validation

* test(settings): fix tests

* changelog: add PR entry

* test(settings): format tests

Co-authored-by: Antonio David Gutiérrez <[email protected]>
Co-authored-by: Álex <[email protected]>
(cherry picked from commit ce29034)
  • Loading branch information
asteriscos authored and github-actions[bot] committed Oct 31, 2022
1 parent 5eb3cba commit abe1233
Show file tree
Hide file tree
Showing 13 changed files with 823 additions and 570 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Redesign the SCA table from agent's dashboard [#4512](https:/wazuh/wazuh-kibana-app/pull/4512)
- Enhanced the plugin setting description displayed in the UI and the configuration file. [#4501](https:/wazuh/wazuh-kibana-app/pull/4501)
- Added validation to the plugin settings in the form of `Settings/Configuration` and the endpoint to update the plugin configuration [#4503](https:/wazuh/wazuh-kibana-app/pull/4503)
- Added new plugin settings to customize the header and footer on the PDF reports [#4505](https:/wazuh/wazuh-kibana-app/pull/4505)

### Changed

Expand Down
55 changes: 52 additions & 3 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,11 @@ export enum SettingCategory {
CUSTOMIZATION,
};

type TPluginSettingOptionsTextArea = {
rowsSize?: number
maxLength?: number
};

type TPluginSettingOptionsSelect = {
select: { text: string, value: any }[]
};
Expand Down Expand Up @@ -441,7 +446,13 @@ export type TPluginSetting = {
// Modify the setting requires restarting the plugin platform to take effect.
requiresRestartingPluginPlatform?: boolean
// Define options related to the `type`.
options?: TPluginSettingOptionsNumber | TPluginSettingOptionsEditor | TPluginSettingOptionsFile | TPluginSettingOptionsSelect | TPluginSettingOptionsSwitch
options?:
TPluginSettingOptionsEditor |
TPluginSettingOptionsFile |
TPluginSettingOptionsNumber |
TPluginSettingOptionsSelect |
TPluginSettingOptionsSwitch |
TPluginSettingOptionsTextArea
// Transform the input value. The result is saved in the form global state of Settings/Configuration
uiFormTransformChangedInputValue?: (value: any) => any
// Transform the configuration value or default as initial value for the input in Settings/Configuration
Expand Down Expand Up @@ -544,7 +555,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = {
validateBackend: function(schema){
return schema.boolean();
},

},
"checks.fields": {
title: "Known fields",
Expand Down Expand Up @@ -1071,6 +1081,46 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = {
)(value)
},
},
"customization.reports.footer": {
title: "Reports footer",
description: "Set the footer of the reports.",
category: SettingCategory.CUSTOMIZATION,
type: EpluginSettingType.textarea,
defaultValue: "",
defaultValueIfNotSet: REPORTS_PAGE_FOOTER_TEXT,
isConfigurableFromFile: true,
isConfigurableFromUI: true,
options: { rowsSize: 2, maxLength: 30 },
validate: function (value) {
return SettingsValidator.multipleLinesString({
max: this.options.rowsSize,
maxLength: this.options.maxLength
})(value)
},
validateBackend: function (schema) {
return schema.string({ validate: this.validate.bind(this) });
},
},
"customization.reports.header": {
title: "Reports header",
description: "Set the header of the reports.",
category: SettingCategory.CUSTOMIZATION,
type: EpluginSettingType.textarea,
defaultValue: "",
defaultValueIfNotSet: REPORTS_PAGE_HEADER_TEXT,
isConfigurableFromFile: true,
isConfigurableFromUI: true,
options: { rowsSize: 3, maxLength: 20 },
validate: function (value) {
return SettingsValidator.multipleLinesString({
max: this.options.rowsSize,
maxLength: this.options?.maxLength
})(value)
},
validateBackend: function(schema){
return schema.string({validate: this.validate?.bind(this)});
},
},
"disabled_roles": {
title: "Disable roles",
description: "Disabled the plugin visibility for users with the roles.",
Expand Down Expand Up @@ -1784,7 +1834,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = {

export type TPluginSettingKey = keyof typeof PLUGIN_SETTINGS;


export enum HTTP_STATUS_CODES {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
Expand Down
468 changes: 239 additions & 229 deletions common/plugin-settings.test.ts

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions common/services/settings-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,17 @@ export class SettingsValidator {
* @param options
* @returns
*/
static multipleLinesString(options: { min?: number, max?: number } = {}) {
static multipleLinesString(options: { min?: number, max?: number, maxLength?: number } = {}) {
return function (value: number) {
const lines = value.split(/\r\n|\r|\n/).length;
if (typeof options.maxLength !== 'undefined' && value.split('\n').some(line => line.length > options.maxLength)) {
return `The maximum length of a line is ${options.maxLength} characters.`;
};
if (typeof options.min !== 'undefined' && lines < options.min) {
return `The string should have more or ${options.min} line/s.`;
};
if (typeof options.max !== 'undefined' && lines > options.max) {
return `The string should have less or ${options.max} line/s.`;
return `The string should have less or equal to ${options.max} line/s.`;
};
}
};
Expand Down
11 changes: 11 additions & 0 deletions public/components/common/form/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,14 @@ exports[`[component] InputForm Renders correctly to match the snapshot: Input: t
</div>
</div>
`;

exports[`[component] InputForm Renders correctly to match the snapshot: Input: textarea 1`] = `
<div>
<textarea
class="euiTextArea euiTextArea--resizeVertical euiTextArea--fullWidth"
rows="6"
>
test
</textarea>
</div>
`;
15 changes: 8 additions & 7 deletions public/components/common/form/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ describe('[component] InputForm', () => {
const optionsSelect = {select: [{text: 'Label1', value: 'value1'}, {text: 'Label2', value: 'value2'}]};
const optionsSwitch = {switch: {values: {enabled: {label: 'Enabled', value: true}, disabled: {label: 'Disabled', value: false}}}};
it.each`
inputType | value | options
${'editor'} | ${'{}'} | ${optionsEditor}
${'filepicker'} | ${'{}'} | ${optionsFilepicker}
${'number'} | ${4} | ${undefined}
${'select'} | ${'value1'} | ${optionsSelect}
${'switch'} | ${true} | ${optionsSwitch}
${'text'} | ${'test'} | ${undefined}
inputType | value | options
${'editor'} | ${'{}'} | ${optionsEditor}
${'filepicker'} | ${'{}'} | ${optionsFilepicker}
${'number'} | ${4} | ${undefined}
${'select'} | ${'value1'} | ${optionsSelect}
${'switch'} | ${true} | ${optionsSwitch}
${'text'} | ${'test'} | ${undefined}
${'textarea'} | ${'test'} | ${undefined}
`('Renders correctly to match the snapshot: Input: $inputType', ({ inputType, value, options }) => {
const wrapper = render(
<InputForm
Expand Down
2 changes: 2 additions & 0 deletions public/components/common/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { InputFormText } from './input_text';
import { InputFormSelect } from './input_select';
import { InputFormSwitch } from './input_switch';
import { InputFormFilePicker } from './input_filepicker';
import { InputFormTextArea } from './input_text_area';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';

export const InputForm = ({
Expand Down Expand Up @@ -61,5 +62,6 @@ const Input = {
number: InputFormNumber,
select: InputFormSelect,
text: InputFormText,
textarea: InputFormTextArea,
filepicker: InputFormFilePicker
};
15 changes: 15 additions & 0 deletions public/components/common/form/input_text_area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { EuiTextArea } from '@elastic/eui';
import { IInputFormType } from './types';

export const InputFormTextArea = ({ value, isInvalid, onChange, options } : IInputFormType) => {
return (
<EuiTextArea
fullWidth
value={value}
isInvalid={isInvalid}
onChange={onChange}
rows={options?.rowsSize}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ export const Category: React.FunctionComponent<ICategoryProps> = ({
aria-label={item.key}
content='Invalid' />
)}

{isUpdated && (
<EuiIconTip
anchorClassName="mgtAdvancedSettings__fieldTitleUnsavedIcon"
Expand Down
2 changes: 1 addition & 1 deletion public/components/settings/configuration/configuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const WzConfigurationSettingsProvider = (props) => {
// Update the settings that uploads a file
if(Object.keys(settingsToUpdate.fileUpload).length){
requests.push(...Object.entries(settingsToUpdate.fileUpload)
.map(([pluginSettingKey, {file, extension}]) => {
.map(([pluginSettingKey, {file}]) => {
// Create the form data
const formData = new FormData();
formData.append('file', file);
Expand Down
67 changes: 41 additions & 26 deletions server/lib/reporting/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import {
import { log } from '../logger';
import * as TimSort from 'timsort';
import { getConfiguration } from '../get-configuration';
import { REPORTS_PRIMARY_COLOR, REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, REPORTS_PAGE_FOOTER_TEXT, REPORTS_PAGE_HEADER_TEXT } from '../../../common/constants';
import { REPORTS_PRIMARY_COLOR} from '../../../common/constants';
import { getSettingDefaultValue } from '../../../common/services/settings';

const COLORS = {
PRIMARY: REPORTS_PRIMARY_COLOR
};

const pageConfiguration = (nameLogo) => ({
const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({
styles: {
h1: {
fontSize: 22,
Expand Down Expand Up @@ -54,11 +55,11 @@ const pageConfiguration = (nameLogo) => ({
margin: [40, 20, 0, 0],
columns: [
{
image: path.join(__dirname, `../../../public/assets/${nameLogo}`),
width: 190
image: path.join(__dirname, `../../../public/assets/${pathToLogo}`),
fit: [190, 50]
},
{
text: REPORTS_PAGE_HEADER_TEXT,
text: pageHeader,
alignment: 'right',
margin: [0, 0, 40, 0],
color: COLORS.PRIMARY
Expand All @@ -70,7 +71,7 @@ const pageConfiguration = (nameLogo) => ({
return {
columns: [
{
text: REPORTS_PAGE_FOOTER_TEXT,
text: pageFooter,
color: COLORS.PRIMARY,
margin: [40, 40, 0, 0]
},
Expand Down Expand Up @@ -473,7 +474,7 @@ export class ReportPrinter{
this.addContent(typeof title === 'string' ? { text: title, style: 'h4' } : title)
.addNewLine();
}

if (!items || !items.length) {
this.addContent({
text: 'No results match your search criteria',
Expand All @@ -494,31 +495,31 @@ export class ReportPrinter{
style: 'standard'
}
})
});
});

// 385 is the max initial width per column
let totalLength = columns.length - 1;
const widthColumn = 385/totalLength;
let totalWidth = totalLength * widthColumn;

const widths:(number)[] = [];

for (let step = 0; step < columns.length - 1; step++) {

let columnLength = this.getColumnWidth(columns[step], tableRows, step);

if (columnLength <= Math.round(totalWidth / totalLength)) {
widths.push(columnLength);
totalWidth -= columnLength;
}
}
else {
widths.push(Math.round(totalWidth / totalLength));
totalWidth -= Math.round((totalWidth / totalLength));
}
totalLength--;
}
widths.push('*');

this.addContent({
fontSize: 8,
table: {
Expand Down Expand Up @@ -562,9 +563,9 @@ export class ReportPrinter{
`agents: ${agents}`,
'debug'
);

this.addNewLine();

this.addContent({
text:
'NOTE: This report only includes the authorized agents of the user who generated the report',
Expand Down Expand Up @@ -613,22 +614,36 @@ export class ReportPrinter{
);
}

async print(reportPath: string){
const nameLogo = ( await getConfiguration() )['customization.logo.reports'] || REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH;
async print(reportPath: string) {
return new Promise((resolve, reject) => {
try {
const configuration = getConfiguration();

const document = this._printer.createPdfKitDocument({...pageConfiguration(nameLogo), content: this._content});
await document.pipe(
fs.createWriteStream(reportPath)
);
document.end();
const pathToLogo = configuration['customization.logo.reports'] || getSettingDefaultValue('customization.logo.reports');
const pageHeader = configuration['customization.reports.header'] || getSettingDefaultValue('customization.reports.header');
const pageFooter = configuration['customization.reports.footer'] || getSettingDefaultValue('customization.reports.footer');

const document = this._printer.createPdfKitDocument({ ...pageConfiguration({ pathToLogo, pageHeader, pageFooter }), content: this._content });

document.on('error', reject);
document.on('end', resolve);

document.pipe(
fs.createWriteStream(reportPath)
);
document.end();
} catch (ex) {
reject(ex);
}
});
}

/**
* Returns the width of a given column
*
* @param column
* @param tableRows
* @param step
*
* @param column
* @param tableRows
* @param step
* @returns {number}
*/
getColumnWidth(column, tableRows, index){
Expand Down
Loading

0 comments on commit abe1233

Please sign in to comment.