diff --git a/public/components/agents/sca/inventory.tsx b/public/components/agents/sca/inventory.tsx index d0da6affd3..02e0ea576b 100644 --- a/public/components/agents/sca/inventory.tsx +++ b/public/components/agents/sca/inventory.tsx @@ -36,7 +36,7 @@ import { import { WzRequest } from '../../../react-services/wz-request'; import { formatUIDate } from '../../../react-services/time-service'; import exportCsv from '../../../react-services/wz-csv'; -import { getToasts } from '../../../kibana-services'; +import { getToasts } from '../../../kibana-services'; import { WzSearchBar } from '../../../components/wz-search-bar'; import { RuleText, ComplianceText } from './components'; import _ from 'lodash'; @@ -55,46 +55,55 @@ export class Inventory extends Component { constructor(props) { super(props); const { agent } = this.props; - this.state = { agent, items: [], itemIdToExpandedRowMap: {}, showMoreInfo: false, loading: false, filters: [], pageTableChecks: {pageIndex: 0}, policies: [] } + this.state = { + agent, + items: [], + itemIdToExpandedRowMap: {}, + showMoreInfo: false, + loading: false, + filters: [], + pageTableChecks: { pageIndex: 0 }, + policies: [], + }; this.suggestions = {}; this.columnsPolicies = [ { field: 'name', - name: 'Policy' + name: 'Policy', }, { field: 'description', name: 'Description', - truncateText: true + truncateText: true, }, { field: 'end_scan', name: 'End scan', dataType: 'date', - render: formatUIDate + render: formatUIDate, }, { field: 'pass', name: 'Pass', - width: "100px" + width: '100px', }, { field: 'fail', name: 'Fail', - width: "100px" + width: '100px', }, { field: 'invalid', name: 'Not applicable', - width: "100px" + width: '100px', }, { field: 'score', name: 'Score', - render: score => { + render: (score) => { return `${score}%`; }, - width: "100px" + width: '100px', }, ]; this.columnsChecks = [ @@ -102,18 +111,18 @@ export class Inventory extends Component { field: 'id', name: 'ID', sortable: true, - width: "100px" + width: '100px', }, { field: 'title', name: 'Title', sortable: true, - truncateText: true + truncateText: true, }, { name: 'Target', truncateText: true, - render: item => ( + render: (item) => (
{item.file ? ( @@ -136,37 +145,31 @@ export class Inventory extends Component { Registry: {item.registry} ) : ( - '-' - )} + '-' + )}
- ) + ), }, { field: 'result', name: 'Result', truncateText: true, sortable: true, - width: "150px", + width: '150px', render: this.addHealthResultRender, }, { align: 'right', - width: "40px", + width: '40px', isExpander: true, - render: item => ( + render: (item) => ( this.toggleDetails(item)} - aria-label={ - this.state.itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand' - } - iconType={ - this.state.itemIdToExpandedRowMap[item.id] - ? 'arrowUp' - : 'arrowDown' - } + aria-label={this.state.itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} + iconType={this.state.itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} /> - ) - } + ), + }, ]; } @@ -179,13 +182,14 @@ export class Inventory extends Component { if (match && match[0]) { this.setState({ loading: true }); const id = match[0].split('=')[1]; - const policy = await WzRequest.apiReq( - 'GET', - `/sca/${this.props.agent.id}`, - { "q": "policy_id=" + id } - ); + const policy = await WzRequest.apiReq('GET', `/sca/${this.props.agent.id}`, { + q: 'policy_id=' + id, + }); await this.loadScaPolicy(((((policy || {}).data || {}).data || {}).items || [])[0]); - window.location.href = window.location.href.replace(new RegExp('redirectPolicy=' + '[^&]*'), ''); + window.location.href = window.location.href.replace( + new RegExp('redirectPolicy=' + '[^&]*'), + '' + ); this.setState({ loading: false }); } } catch (error) { @@ -208,9 +212,12 @@ export class Inventory extends Component { async componentDidUpdate(prevProps, prevState) { if (!_.isEqual(this.props.agent, prevProps.agent)) { this.setState({ lookingPolicy: false }, async () => await this.initialize()); - }; - if(!_.isEqual(this.state.filters, prevState.filters)){ - this.setState({itemIdToExpandedRowMap: {}, pageTableChecks: {pageIndex: 0, pageSize: this.state.pageTableChecks.pageSize}}); + } + if (!_.isEqual(this.state.filters, prevState.filters)) { + this.setState({ + itemIdToExpandedRowMap: {}, + pageTableChecks: { pageIndex: 0, pageSize: this.state.pageTableChecks.pageSize }, + }); } } @@ -219,7 +226,7 @@ export class Inventory extends Component { } addHealthResultRender(result) { - const color = result => { + const color = (result) => { if (result.toLowerCase() === 'passed') { return 'success'; } else if (result.toLowerCase() === 'failed') { @@ -236,11 +243,17 @@ export class Inventory extends Component { ); } + /** + * Generate and assign the suggestions for the searchbar + * @param policy + * @param checks + * @returns + */ buildSuggestionSearchBar(policy, checks) { if (this.suggestions[policy]) return; const distinctFields = {}; - checks.forEach(item => { - Object.keys(item).forEach(field => { + checks.forEach((item) => { + Object.keys(item).forEach((field) => { if (typeof item[field] === 'string') { if (!distinctFields[field]) { distinctFields[field] = {}; @@ -252,33 +265,119 @@ export class Inventory extends Component { }); }); - this.suggestions[policy] = [ - { type: 'params', label: 'condition', description: 'Filter by check condition', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["condition"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'file', description: 'Filter by check file', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["file"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'title', description: 'Filter by check title', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["title"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'result', description: 'Filter by check result', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["result"]).filter(item => item && item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'status', description: 'Filter by check status', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["status"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'rationale', description: 'Filter by check rationale', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["rationale"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'registry', description: 'Filter by check registry', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["registry"] || {}).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'description', description: 'Filter by check description', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["description"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'remediation', description: 'Filter by check remediation', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["remediation"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - { type: 'params', label: 'reason', description: 'Filter by check reason', operators: ['=', '!=',], values: (value) => { return Object.keys(distinctFields["reason"]).filter(item => item && item.toLowerCase().includes(value.toLowerCase())) } }, - ] + /** + * Get list of values defined in distinctFields by field + * @param value + * @param field + * @returns + */ + const getSuggestionsValues = (value, field) => { + if (!distinctFields[field]) return []; + return Object.keys(distinctFields[field]).filter( + (item) => item && item.toLowerCase().includes(value.toLowerCase().trim()) + ); + }; + + /** + * Get list of suggestions. + * This method validate if the suggestion item exists in the checks array fields + * @returns List of suggestions + */ + const getSuggestionsFields = () => { + const defaultSuggestions = [ + { + type: 'params', + label: 'condition', + description: 'Filter by check condition', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'condition'), + }, + { + type: 'params', + label: 'file', + description: 'Filter by check file', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'file'), + }, + { + type: 'params', + label: 'title', + description: 'Filter by check title', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'title'), + }, + { + type: 'params', + label: 'result', + description: 'Filter by check result', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'result'), + }, + { + type: 'params', + label: 'status', + description: 'Filter by check status', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'status'), + }, + { + type: 'params', + label: 'rationale', + description: 'Filter by check rationale', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'rationale'), + }, + { + type: 'params', + label: 'registry', + description: 'Filter by check registry', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'registry'), + }, + { + type: 'params', + label: 'description', + description: 'Filter by check description', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'description'), + }, + { + type: 'params', + label: 'remediation', + description: 'Filter by check remediation', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'remediation'), + }, + { + type: 'params', + label: 'reason', + description: 'Filter by check reason', + operators: ['=', '!='], + values: (value) => getSuggestionsValues(value, 'reason'), + }, + ]; + + const filteresSuggestions = defaultSuggestions.filter((item) => + Object.keys(distinctFields).includes(item.label) + ); + return filteresSuggestions; + }; + this.suggestions[policy] = getSuggestionsFields(); } async initialize() { try { this._isMount && this.setState({ loading: true }); this.lookingPolicy = false; - const {data: {data: {affected_items: policies}}} = await WzRequest.apiReq( - 'GET', - `/sca/${this.props.agent.id}`, - {} - ); + const { + data: { + data: { affected_items: policies }, + }, + } = await WzRequest.apiReq('GET', `/sca/${this.props.agent.id}`, {}); this._isMount && this.setState({ loading: false, policies }); } catch (error) { - this.setState({ loading: false, policies: []}); + this.setState({ loading: false, policies: [] }); const options: UIErrorLog = { context: `${Inventory.name}.initialize`, @@ -342,17 +441,24 @@ export class Inventory extends Component { } } - filterPolicyChecks = () => !!this.state.items && this.state.items.filter(check => - this.state.filters.every(filter => + filterPolicyChecks = () => + !!this.state.items && + this.state.items.filter((check) => + this.state.filters.every((filter) => filter.field === 'search' - ? Object.keys(check).some(key => ['string', 'number'].includes(typeof check[key]) && String(check[key]).toLowerCase().includes(filter.value.toLowerCase())) - : typeof check[filter.field] === 'string' && (filter.value === '' ? check[filter.field] === filter.value - : check[filter.field].toLowerCase().includes(filter.value.toLowerCase()) - ) + ? Object.keys(check).some( + (key) => + ['string', 'number'].includes(typeof check[key]) && + String(check[key]).toLowerCase().includes(filter.value.toLowerCase()) + ) + : typeof check[filter.field] === 'string' && + (filter.value === '' + ? check[filter.field] === filter.value + : check[filter.field].toLowerCase().includes(filter.value.toLowerCase())) ) - ) + ); - toggleDetails = item => { + toggleDetails = (item) => { const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap }; if (itemIdToExpandedRowMap[item.id]) { @@ -361,30 +467,31 @@ export class Inventory extends Component { let checks = ''; checks += (item.rules || []).length > 1 ? 'Checks' : 'Check'; checks += item.condition ? ` (Condition: ${item.condition})` : ''; - const complianceText = item.compliance && item.compliance.length - ? item.compliance.map(el => `${el.key}: ${el.value}`).join('\n') - : ''; - const rulesText = item.rules.length ? item.rules.map(el => el.rule).join('\n') : ''; + const complianceText = + item.compliance && item.compliance.length + ? item.compliance.map((el) => `${el.key}: ${el.value}`).join('\n') + : ''; + const rulesText = item.rules.length ? item.rules.map((el) => el.rule).join('\n') : ''; const listItems = [ { title: 'Check not applicable due to:', - description: item.reason + description: item.reason, }, { title: 'Rationale', - description: item.rationale || '-' + description: item.rationale || '-', }, { title: 'Remediation', - description: item.remediation || '-' + description: item.remediation || '-', }, { title: 'Description', - description: item.description || '-' + description: item.description || '-', }, { title: (item.directory || '').includes(',') ? 'Paths' : 'Path', - description: item.directory + description: item.directory, }, { title: checks, @@ -392,15 +499,13 @@ export class Inventory extends Component { }, { title: 'Compliance', - description: - } + description: , + }, ]; - const itemsToShow = listItems.filter(x => { + const itemsToShow = listItems.filter((x) => { return x.description; }); - itemIdToExpandedRowMap[item.id] = ( - - ); + itemIdToExpandedRowMap[item.id] = ; } this.setState({ itemIdToExpandedRowMap }); }; @@ -436,11 +541,11 @@ export class Inventory extends Component { } buttonStat(text, field, value) { - return + return ; } - onChangeTableChecks({ page: {index: pageIndex, size: pageSize} }){ - this.setState({ pageTableChecks: {pageIndex, pageSize} }); + onChangeTableChecks({ page: { index: pageIndex, size: pageSize } }) { + this.setState({ pageTableChecks: { pageIndex, pageSize } }); } render() { @@ -448,34 +553,34 @@ export class Inventory extends Component { return { 'data-test-subj': `sca-row-${idx}`, className: 'customRowClass', - onClick: () => this.loadScaPolicy(item) + onClick: () => this.loadScaPolicy(item), }; }; const getChecksRowProps = (item, idx) => { return { 'data-test-subj': `sca-check-row-${idx}`, className: 'customRowClass', - onClick: () => this.toggleDetails(item) + onClick: () => this.toggleDetails(item), }; }; const sorting = { sort: { field: 'id', - direction: 'asc' - } + direction: 'asc', + }, }; const buttonPopover = ( this.setState({ showMoreInfo: !this.state.showMoreInfo })}> - + onClick={() => this.setState({ showMoreInfo: !this.state.showMoreInfo })} + > ); return (
- {(this.state.loading && + {this.state.loading && (
@@ -483,162 +588,226 @@ export class Inventory extends Component { )}
- {((this.props.agent && (this.props.agent || {}).status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED && !this.state.policies.length && !this.state.loading) && - - this.initialize()}> - Refresh - - - )} + {this.props.agent && + (this.props.agent || {}).status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED && + !this.state.policies.length && + !this.state.loading && ( + + this.initialize()}> + Refresh + + + )} - {((this.props.agent && (this.props.agent || {}).status === API_NAME_AGENT_STATUS.NEVER_CONNECTED && !this.state.loading) && - - this.initialize()}> - Refresh - - - )} - {((this.props.agent && (this.props.agent || {}).os && !this.state.lookingPolicy && this.state.policies.length > 0 && !this.state.loading) && -
- {this.state.policies.length && - - {this.state.policies.map((policy, idx) => ( - - - - - - - ))} - - } - - - - - - - - -
- )} - {((this.props.agent && (this.props.agent || {}).os && this.state.lookingPolicy && !this.state.loading) && -
- - - - this.loadScaPolicy(false)} - iconType="arrowLeft" - aria-label="Back to policies" - {...{ iconSize: 'l' }} - /> - - - -

{this.state.lookingPolicy.name}  - - this.setState({ showMoreInfo: false })}> - - - - Policy description: {this.state.lookingPolicy.description} -

- Policy checksum: {this.state.lookingPolicy.hash_file} -
-
-
-
-

-
-
- - await this.downloadCsv()} > - Export formatted - - - - this.loadScaPolicy(this.state.lookingPolicy)}> - Refresh - - -
- - - - - - - - - - - - - - - - - - + {this.props.agent && + (this.props.agent || {}).status === API_NAME_AGENT_STATUS.NEVER_CONNECTED && + !this.state.loading && ( + + this.initialize()}> + Refresh + + + )} + {this.props.agent && + (this.props.agent || {}).os && + !this.state.lookingPolicy && + this.state.policies.length > 0 && + !this.state.loading && ( +
+ {this.state.policies.length && ( + + {this.state.policies.map((policy, idx) => ( + + + + + + + ))} + + )} + + + + + + + +
+ )} + {this.props.agent && + (this.props.agent || {}).os && + this.state.lookingPolicy && + !this.state.loading && ( +
+ + + + this.loadScaPolicy(false)} + iconType="arrowLeft" + aria-label="Back to policies" + {...{ iconSize: 'l' }} + /> + + + +

+ {this.state.lookingPolicy.name}  + + this.setState({ showMoreInfo: false })} + > + + + + Policy description: {this.state.lookingPolicy.description} +

+ Policy checksum: {this.state.lookingPolicy.hash_file} +
+
+
+
+

+
+
+ + await this.downloadCsv()} + > + Export formatted + + + + this.loadScaPolicy(this.state.lookingPolicy)} + > + Refresh + + +
+ + + + + + + + + + + + + + + + + + + - - - { this.setState({ filters }) }} /> - - + + + { + this.setState({ filters }); + }} + /> + + - - - this.onChangeTableChecks(change)} - /> - - -
-
- )} + + + this.onChangeTableChecks(change)} + /> + + +
+
+ )}
); diff --git a/public/components/wz-search-bar/lib/suggest-handler.ts b/public/components/wz-search-bar/lib/suggest-handler.ts index d4e9251a88..f70f2d6fce 100644 --- a/public/components/wz-search-bar/lib/suggest-handler.ts +++ b/public/components/wz-search-bar/lib/suggest-handler.ts @@ -47,7 +47,7 @@ export class SuggestHandler extends BaseHandler { this.filters = props.filters; this.inputStage = 'field'; this.setInputValue = setInputValue; - this.suggestItems = props.suggestions; + this.suggestItems = props.suggestions || []; this.searchType = 'search'; this.lastCall = 0; if (inputRef) this.inputRef = inputRef;