From 8f136382aba217b2a7996a1001f18ddf57b31e13 Mon Sep 17 00:00:00 2001 From: "chantal.kelm" Date: Mon, 8 Jul 2024 09:42:23 -0300 Subject: [PATCH] First iteration of poc --- .../common/modules/modules-defaults.tsx | 24 +- .../components/check-module-enabled.tsx | 115 ++++++++ .../document-view-table-and-json.tsx | 40 +++ .../common/components/loading_spinner.scss | 4 + .../common/components/loading_spinner.tsx | 21 ++ .../common/components/no_results.tsx | 60 ++++ ...e-vulnerabilities-states-index-pattern.tsx | 161 ++++++++++ .../common/hooks/useCheckIndexFields.tsx | 46 +++ .../dashboards/index.ts | 2 + .../dashboards/inventory/config/index.ts | 24 ++ .../dashboards/inventory/index.tsx | 1 + .../dashboards/inventory/inventory.scss | 27 ++ .../dashboards/inventory/inventory.tsx | 275 ++++++++++++++++++ .../overview/dashboard-saved-object.tsx | 80 +++++ .../dashboards/overview/dashboard.tsx | 7 + .../dashboards/overview/index.tsx | 1 + .../events/vulnerabilities-columns.tsx | 22 ++ .../poc-dashboards-by-reference/index.tsx | 1 + 18 files changed, 909 insertions(+), 2 deletions(-) create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/check-module-enabled.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/document-view-table-and-json.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.scss create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/no_results.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/hocs/validate-vulnerabilities-states-index-pattern.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/common/hooks/useCheckIndexFields.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/index.ts create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/config/index.ts create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/index.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.scss create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard-saved-object.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/index.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/events/vulnerabilities-columns.tsx create mode 100644 plugins/main/public/components/overview/poc-dashboards-by-reference/index.tsx diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index 9dffb7e294..a340bb86a8 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -18,7 +18,11 @@ import { ComplianceTable } from '../../overview/compliance-table'; import { ButtonModuleGenerateReport } from '../modules/buttons'; import { OfficePanel } from '../../overview/office/panel'; import { GitHubPanel } from '../../overview/github/panel'; -import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities'; +// import { InventoryVuls } from '../../overview/vulnerabilities'; +import { + DashboardVuls, + InventoryVuls, +} from '../../overview/poc-dashboards-by-reference/dashboards'; import { DashboardMITRE } from '../../overview/mitre/dashboard'; import { withModuleNotForAgent } from '../hocs'; import { @@ -86,7 +90,6 @@ const renderDiscoverTab = (props: WazuhDiscoverProps) => { component: () => , }; }; - export const ModulesDefaults = { general: { init: 'events', @@ -245,6 +248,23 @@ export const ModulesDefaults = { vuls: { init: 'dashboard', tabs: [ + // { + // id: 'dashboard', + // name: 'Dashboard', + // component: DashboardSavedObject, + // /* For ButtonExploreAgent to insert correctly according to the module's index pattern, the moduleIndexPatternTitle parameter is added. By default it applies the index patternt wazuh-alerts-* */ + // buttons: [ + // (...props) => { + // console.log('ButtonExploreAgent Props in vuls:', props); + // return ( + // + // ); + // }, + // ], + // }, { id: 'dashboard', name: 'Dashboard', diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/check-module-enabled.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/check-module-enabled.tsx new file mode 100644 index 0000000000..f254dbde95 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/check-module-enabled.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from 'react'; +import { + clusterReq, + clusterNodes, +} from '../../../../../controllers/management/components/management/configuration/utils/wz-fetch'; +import { WzRequest } from '../../../../../react-services'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../../../react-services/common-services'; +import { useUserPermissionsRequirements } from '../../../../common/hooks'; + +async function checkVDIsEnabledCluster() { + // Get nodes + const responseNodes = await clusterNodes(); + + const nodes = responseNodes.data.data.affected_items.map(({ name }) => name); + + // Check if at least some of the nodes has the module enabled + for (const node of nodes) { + const responseNodeWmodules = await WzRequest.apiReq( + 'GET', + `/cluster/${node}/configuration/wmodules/wmodules`, + {}, + ); + const vdConfiguration = + responseNodeWmodules.data.data?.affected_items?.[0]?.wmodules?.find( + ({ ['vulnerability-detection']: wmodule }) => wmodule, + ); + if (vdConfiguration?.['vulnerability-detection']?.enabled === 'yes') { + return true; + } + } + return false; +} + +async function checkVDIsEnabledManager() { + const responseWmodules = await WzRequest.apiReq( + 'GET', + `/manager/configuration/wmodules/wmodules`, + {}, + ); + + const vdConfiguration = + responseWmodules.data.data?.affected_items?.[0]?.wmodules?.find( + ({ ['vulnerability-detection']: wmodule }) => wmodule, + ); + return vdConfiguration?.['vulnerability-detection']?.enabled === 'yes'; +} + +export const ModuleEnabledCheck = () => { + const [data, setData] = useState<{ enabled: boolean } | null>(null); + const [userPermissionRequirements] = useUserPermissionsRequirements([ + { action: 'cluster:status', resource: '*:*:*' }, + { action: 'cluster:read', resource: 'node:id:*' }, + { action: 'manager:read', resource: '*:*:*' }, + ]); + + const checkVDIsEnabled = async () => { + try { + // Check cluster status + setData(null); + const clusterStatus = await clusterReq(); + + // Check if the module is enabled + const enabled = + clusterStatus.data.data.enabled === 'yes' && + clusterStatus.data.data.running === 'yes' + ? await checkVDIsEnabledCluster() + : await checkVDIsEnabledManager(); + setData({ enabled }); + } catch (error) { + const options = { + context: `${ModuleEnabledCheck.name}.useEffect`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + error: { + error: error, + message: error.message || error, + title: 'Error checking if the module is enabled', + }, + }; + getErrorOrchestrator().handleError(options); + } + }; + + useEffect(() => { + /* Only check if the module is enabled if the user has the expected permissions to + do the API requests */ + if (!userPermissionRequirements) { + checkVDIsEnabled(); + } + }, [userPermissionRequirements]); + + return data?.enabled === false ? ( + +

+ Vulnerabilies detection module is not enabled. You can learn to how to + configure following the{' '} + + documentation + + . +

+
+ ) : null; +}; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/document-view-table-and-json.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/document-view-table-and-json.tsx new file mode 100644 index 0000000000..c82e73eef8 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/document-view-table-and-json.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { EuiFlexItem, EuiCodeBlock, EuiTabbedContent } from '@elastic/eui'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; +import DocViewer from '../../../../common/doc-viewer/doc-viewer'; +import { useDocViewer } from '../../../../common/doc-viewer'; + +export const DocumentViewTableAndJson = ({ document, indexPattern }) => { + const docViewerProps = useDocViewer({ + doc: document, + indexPattern: indexPattern as IndexPattern, + }); + + return ( + + , + }, + { + id: 'json', + name: 'JSON', + content: ( + + {JSON.stringify(document, null, 2)} + + ), + }, + ]} + /> + + ); +}; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.scss b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.scss new file mode 100644 index 0000000000..051ab642c1 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.scss @@ -0,0 +1,4 @@ +.discoverNoResults { + display: flex; + align-items: center; +} diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.tsx new file mode 100644 index 0000000000..7f505e6167 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/loading_spinner.tsx @@ -0,0 +1,21 @@ +import './loading_spinner.scss'; +import React from 'react'; +import { EuiTitle, EuiPanel, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedMessage } from '@osd/i18n/react'; + +export function LoadingSpinner() { + return ( + + } + title={ + +

+ +

+
+ } + /> +
+ ); +} diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/no_results.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/no_results.tsx new file mode 100644 index 0000000000..babfd51d32 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/components/no_results.tsx @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; + +import { EuiCallOut, EuiPanel } from '@elastic/eui'; + +interface Props { + message?: string; +} + +export const DiscoverNoResults = ({ message }: Props) => { + return ( + + + + ) + } + color='warning' + iconType='help' + data-test-subj='discoverNoResults' + /> + + + ); +}; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/hocs/validate-vulnerabilities-states-index-pattern.tsx new file mode 100644 index 0000000000..c5c819eab2 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/hocs/validate-vulnerabilities-states-index-pattern.tsx @@ -0,0 +1,161 @@ +import React from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { withGuardAsync } from '../../../../common/hocs'; +import { getSavedObjects } from '../../../../../kibana-services'; +import { SavedObject } from '../../../../../react-services'; +import { NOT_TIME_FIELD_NAME_INDEX_PATTERN } from '../../../../../../common/constants'; +import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { vulnerabilityDetection } from '../../../../../utils/applications'; +import { LoadingSpinnerDataSource } from '../../../../common/loading/loading-spinner-data-source'; +import NavigationService from '../../../../../react-services/navigation-service'; + +const INDEX_PATTERN_CREATION_NO_INDEX = 'INDEX_PATTERN_CREATION_NO_INDEX'; + +async function checkExistenceIndexPattern(indexPatternID: string) { + return await getSavedObjects().client.get('index-pattern', indexPatternID); +} + +async function checkExistenceIndices(indexPatternId: string) { + try { + const fields = await SavedObject.getIndicesFields(indexPatternId); + return { exist: true, fields }; + } catch (error) { + return { exist: false }; + } +} + +async function createIndexPattern(indexPattern, fields: any) { + try { + await SavedObject.createSavedObject( + 'index-pattern', + indexPattern, + { + attributes: { + title: indexPattern, + timeFieldName: NOT_TIME_FIELD_NAME_INDEX_PATTERN, + }, + }, + fields, + ); + await SavedObject.validateIndexPatternSavedObjectCanBeFound([indexPattern]); + } catch (error) { + return { error: error.message }; + } +} + +export async function validateVulnerabilitiesStateDataSources({ + vulnerabilitiesStatesindexPatternID: indexPatternID, +}) { + try { + // Check the existence of related index pattern + const existIndexPattern = await checkExistenceIndexPattern(indexPatternID); + let indexPattern = existIndexPattern; + + // If the idnex pattern does not exist, then check the existence of index + if (existIndexPattern?.error?.statusCode === 404) { + // Check the existence of indices + const { exist, fields } = await checkExistenceIndices(indexPatternID); + + if (!exist) { + return { + ok: true, + data: { + error: { + title: + 'Vulnerability detection seems to be disabled or has a problem', + type: INDEX_PATTERN_CREATION_NO_INDEX, + }, + }, + }; + } + // If the some index match the index pattern, then create the index pattern + const resultCreateIndexPattern = await createIndexPattern( + indexPatternID, + fields, + ); + if (resultCreateIndexPattern?.error) { + return { + ok: true, + data: { + error: { + title: 'There was a problem creating the index pattern', + message: resultCreateIndexPattern?.error, + }, + }, + }; + } + /* WORKAROUND: Redirect to the root of Vulnerabilities Detection application that should + redirects to the Dashboard tab. We want to redirect to this view, because we need the + component is visible (visualizations) to ensure the process that defines the filters for the + Events tab is run when the Dashboard component is unmounted. This workaround solves a + problem in the Events tabs related there are no implicit filters when accessing if the HOC + that protect the view is passed. + */ + NavigationService.getInstance().navigateToApp(vulnerabilityDetection.id); + } + return { + ok: false, + data: { indexPattern }, + }; + } catch (error) { + return { + ok: true, + data: { + error: { title: 'There was a problem', message: error.message }, + }, + }; + } +} + +const errorPromptBody = { + INDEX_PATTERN_CREATION_NO_INDEX: ( +

+ Please check the cluster status. Also, you can check the{' '} + + vulnerability detection documentation. + +

+ ), +}; + +export const PromptCheckIndex = props => { + const { refresh } = props; + const { title, message } = props?.error; + const body = errorPromptBody?.[props?.error?.type] ||

{message}

; + + return ( + {title}} + body={body} + actions={ + + Refresh + + } + /> + ); +}; + +const mapStateToProps = state => ({ + vulnerabilitiesStatesindexPatternID: + state.appConfig.data['vulnerabilities.pattern'], +}); + +export const withVulnerabilitiesStateDataSource = compose( + connect(mapStateToProps), + withGuardAsync( + validateVulnerabilitiesStateDataSources, + ({ error, check }) => , + () => , + ), +); diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/common/hooks/useCheckIndexFields.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/hooks/useCheckIndexFields.tsx new file mode 100644 index 0000000000..c8ba68c25f --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/common/hooks/useCheckIndexFields.tsx @@ -0,0 +1,46 @@ +import { useState, useEffect } from 'react'; +import { SavedObject } from '../../../../../react-services'; + +interface UseCheckIndexFieldsResult { + isLoading: boolean; + isSuccess: boolean; + isError: boolean; + error: Error | null; + resultIndexData: any; +} + +const useCheckIndexFields = (indexPatternId: string, indexType: string) => { + const [isError, setIsError] = useState(false); + const [error, setError] = useState(null); + const [isSuccess, setIsSuccess] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (indexPatternId) { + const checkIndexFields = async () => { + try { + // Check that the index exists + await SavedObject.getIndicesFields(indexPatternId, indexType); + setIsSuccess(true); + setIsLoading(false); + } catch (error) { + setError(error); + setIsError(true); + setIsSuccess(false); + setIsLoading(false); + } + }; + + checkIndexFields(); + } + }, [indexPatternId, indexType]); + + return { + isError, + error, + isSuccess, + isLoading, + } as UseCheckIndexFieldsResult; +}; + +export default useCheckIndexFields; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/index.ts b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/index.ts new file mode 100644 index 0000000000..a3071fa116 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/index.ts @@ -0,0 +1,2 @@ +export * from './overview'; +export * from './inventory'; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/config/index.ts b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/config/index.ts new file mode 100644 index 0000000000..c0c9506fbe --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/config/index.ts @@ -0,0 +1,24 @@ +import { EuiDataGridColumn } from '@elastic/eui'; + +export const MAX_ENTRIES_PER_QUERY = 10000; + +export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [ + { + id: 'agent.name', + }, + { + id: 'package.name', + }, + { + id: 'package.version', + }, + { + id: 'vulnerability.description', + }, + { + id: 'vulnerability.severity', + }, + { + id: 'vulnerability.id', + }, +]; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/index.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/index.tsx new file mode 100644 index 0000000000..ddb0742f5e --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/index.tsx @@ -0,0 +1 @@ +export * from './inventory'; \ No newline at end of file diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.scss b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.scss new file mode 100644 index 0000000000..4844141f13 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.scss @@ -0,0 +1,27 @@ +.vulsInventoryContainer { + height: calc(100vh - 104px); + + .euiDataGrid__virtualized { + height: max-content !important; + } + + // This makes the table not generate an unnecessary scroll when filtering data from 1 page and then do another search for more pages. + .vulsInventoryDataGrid { + height: calc(100vh - 216px) !important; + } + + .euiDataGrid--fullScreen { + height: calc(100vh - 49px); + bottom: 0; + top: auto; + + .euiDataGrid__virtualized { + height: calc(100vh - 147px) !important; + width: 100% !important; + } + } +} + +.headerIsExpanded .vulsInventoryContainer { + height: calc(100vh - 153px); +} diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.tsx new file mode 100644 index 0000000000..2bb5edc065 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/inventory/inventory.tsx @@ -0,0 +1,275 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { IntlProvider } from 'react-intl'; +import { + EuiDataGrid, + EuiPageTemplate, + EuiToolTip, + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiFlexGroup, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, + EuiPanel, +} from '@elastic/eui'; +import { SearchResponse } from '../../../../../../../../src/core/server'; +import { HitsCounter } from '../../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; +import { formatNumWithCommas } from '../../../../../kibana-integrations/discover/application/helpers'; +import { getWazuhCorePlugin } from '../../../../../kibana-services'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../../../react-services/error-management'; +import './inventory.scss'; +import { MAX_ENTRIES_PER_QUERY, inventoryTableDefaultColumns } from './config'; +import { DiscoverNoResults } from '../../common/components/no_results'; +import { LoadingSpinner } from '../../common/components/loading_spinner'; +// common components/hooks +import useSearchBar from '../../../../common/search-bar/use-search-bar'; +import { useDataGrid } from '../../../../common/data-grid/use-data-grid'; +import { useDocViewer } from '../../../../common/doc-viewer/use-doc-viewer'; +import { withErrorBoundary } from '../../../../common/hocs'; +import { exportSearchToCSV } from '../../../../common/data-grid/data-grid-service'; +import { compose } from 'redux'; +import { withVulnerabilitiesStateDataSource } from '../../common/hocs/validate-vulnerabilities-states-index-pattern'; +import { ModuleEnabledCheck } from '../../common/components/check-module-enabled'; + +import { + VulnerabilitiesDataSourceRepository, + VulnerabilitiesDataSource, + tParsedIndexPattern, + PatternDataSource, +} from '../../../../common/data-source'; +import { useDataSource } from '../../../../common/data-source/hooks'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; +import { DocumentViewTableAndJson } from '../../common/components/document-view-table-and-json'; +import { wzDiscoverRenderColumns } from '../../../../common/wazuh-discover/render-columns'; +import { WzSearchBar } from '../../../../common/search-bar'; + +const InventoryVulsComponent = () => { + const { + dataSource, + filters, + fetchFilters, + isLoading: isDataSourceLoading, + fetchData, + setFilters, + } = useDataSource({ + DataSource: VulnerabilitiesDataSource, + repository: new VulnerabilitiesDataSourceRepository(), + }); + const { searchBarProps } = useSearchBar({ + indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters, + }); + const { query } = searchBarProps; + + const [results, setResults] = useState({} as SearchResponse); + const [inspectedHit, setInspectedHit] = useState(undefined); + const [indexPattern, setIndexPattern] = useState( + undefined, + ); + const [isExporting, setIsExporting] = useState(false); + + const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav(); + + const onClickInspectDoc = useMemo( + () => (index: number) => { + const rowClicked = results.hits.hits[index]; + setInspectedHit(rowClicked); + }, + [results], + ); + + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { + const inspectHintMsg = 'Inspect vulnerability details'; + return ( + + onClickInspectDoc(rowIndex)} + iconType='inspect' + aria-label={inspectHintMsg} + /> + + ); + }; + + const dataGridProps = useDataGrid({ + ariaLabelledBy: 'Vulnerabilities Inventory Table', + defaultColumns: inventoryTableDefaultColumns, + renderColumns: wzDiscoverRenderColumns, + results, + indexPattern: indexPattern as IndexPattern, + DocViewInspectButton, + }); + + const { pagination, sorting, columnVisibility } = dataGridProps; + + const docViewerProps = useDocViewer({ + doc: inspectedHit, + indexPattern: indexPattern as IndexPattern, + }); + + const onClickExportResults = async () => { + const params = { + indexPattern: indexPattern as IndexPattern, + filters: fetchFilters, + query, + fields: columnVisibility.visibleColumns, + pagination: { + pageIndex: 0, + pageSize: results.hits.total, + }, + sorting, + }; + try { + setIsExporting(true); + await exportSearchToCSV(params); + } catch (error) { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error downloading csv report', + }); + ErrorHandler.handleError(searchError); + } finally { + setIsExporting(false); + } + }; + + useEffect(() => { + if (isDataSourceLoading) { + return; + } + setIndexPattern(dataSource?.indexPattern); + fetchData({ query, pagination, sorting }) + .then(results => { + setResults(results); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching vulnerabilities', + }); + ErrorHandler.handleError(searchError); + }); + }, [ + JSON.stringify(fetchFilters), + JSON.stringify(query), + JSON.stringify(pagination), + JSON.stringify(sorting), + ]); + console.log('hola'); + return ( + + <> + + + <> + {isDataSourceLoading ? ( + + ) : ( + + )} + {!isDataSourceLoading && results?.hits?.total === 0 ? ( + + ) : null} + {!isDataSourceLoading && results?.hits?.total > 0 ? ( + +
+ + MAX_ENTRIES_PER_QUERY + ? { + ariaLabel: 'Warning', + content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas( + MAX_ENTRIES_PER_QUERY, + )} hits.`, + iconType: 'alert', + position: 'top', + } + : undefined + } + /> + + Export Formated + + + ), + }} + /> +
+
+ ) : null} + {inspectedHit && ( + setInspectedHit(undefined)} size='m'> + + +

Vulnerability details

+
+
+ + + + + +
+ )} + +
+ +
+ ); +}; + +export const InventoryVuls = compose( + withErrorBoundary, + withVulnerabilitiesStateDataSource, +)(InventoryVulsComponent); diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard-saved-object.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard-saved-object.tsx new file mode 100644 index 0000000000..a53b2ae176 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard-saved-object.tsx @@ -0,0 +1,80 @@ +import React, { useState, useEffect } from 'react'; +import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; +import { GenericRequest } from '../../../../../react-services/generic-request'; +import { getPlugins } from '../../../../../kibana-services'; + +const DashboardByRenderer = + getPlugins().dashboard.DashboardContainerByValueRenderer; + +const transformPanelsJSON = ({ panelsJSON, references }) => + Object.fromEntries( + JSON.parse(panelsJSON).map(({ gridData, panelIndex, panelRefName }) => [ + panelIndex, + { + gridData: gridData, + type: 'visualization', + explicitInput: { + id: panelIndex, + savedObjectId: references.find(({ name }) => name === panelRefName) + .id, + }, + }, + ]), + ); + +const transform = spec => { + const options = JSON.parse(spec.attributes.optionsJSON); + return { + title: spec.attributes.title, + panels: transformPanelsJSON({ + panelsJSON: spec.attributes.panelsJSON, + references: spec.references, + }), + useMargins: options.useMargins, + hidePanelTitles: options.hidePanelTitles, + description: spec.attributes.description, + id: spec.id, + }; +}; + +export const DashboardSavedObject = ({ savedObjectId, ...props }) => { + const [dashboardSpecForComponent, setDashboardSpecForComponent] = useState< + null | string + >(null); + useEffect(() => { + // Get dashboard saved object specification + (async () => { + console.log({ props }, 'randadsa'); + const { data } = await GenericRequest.request( + 'GET', + `/api/saved_objects/dashboard/${savedObjectId}`, + ); + + // Tranform to the expected by the render component + const dashboardSpecRenderer = transform(data); + + setDashboardSpecForComponent(dashboardSpecRenderer); + console.log(dashboardSpecRenderer, 'dashboardSpecRenderer'); + })(); + }, []); + return dashboardSpecForComponent ? ( + + ) : null; +}; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard.tsx new file mode 100644 index 0000000000..b2a14f8a66 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/dashboard.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { DashboardSavedObject } from './dashboard-saved-object'; + +export const DashboardVuls = () => { + const savedObjectId = 'c69f6ea0-3893-11ef-a08d-93dcf854882b'; + return ; +}; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/index.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/index.tsx new file mode 100644 index 0000000000..b58b6c9229 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/dashboards/overview/index.tsx @@ -0,0 +1 @@ +export * from './dashboard'; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/events/vulnerabilities-columns.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/events/vulnerabilities-columns.tsx new file mode 100644 index 0000000000..e4d0a53548 --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/events/vulnerabilities-columns.tsx @@ -0,0 +1,22 @@ +import { tDataGridColumn } from '../../../common/data-grid'; + +export const vulnerabilitiesColumns: tDataGridColumn[] = [ + { + id: 'timestamp', + }, + { + id: 'agent.name', + }, + { + id: 'data.vulnerability.package.name', + }, + { + id: 'data.vulnerability.cve', + }, + { + id: 'data.vulnerability.severity', + }, + { + id: 'data.vulnerability.status', + }, +]; diff --git a/plugins/main/public/components/overview/poc-dashboards-by-reference/index.tsx b/plugins/main/public/components/overview/poc-dashboards-by-reference/index.tsx new file mode 100644 index 0000000000..d46e147b8e --- /dev/null +++ b/plugins/main/public/components/overview/poc-dashboards-by-reference/index.tsx @@ -0,0 +1 @@ +export * from './dashboards'; \ No newline at end of file