From a1fc4909f0cd4869233462afb926e1e32c1b8e98 Mon Sep 17 00:00:00 2001
From: Maximiliano Ibarra <6089438+Machi3mfl@users.noreply.github.com>
Date: Thu, 22 Aug 2024 08:47:55 -0300
Subject: [PATCH] POC Spike Fleet Management (#6935)
* New plugin wazuh-fleet
* Implement wazuh-fleet sidebar and fleet list components
* Add agents grid
* Add fields to script
* Add visualizations in agents summary
* Improve agent list
* Add agent details page and flyout
* Add groups index script
* Add groups data source
* Add groups components to router
* Add group table
* Add command index script
* Add commands list in fleet management
* Fix get agent details
* Improve and fix minor issues
* Add host and networks to script and improve agent view
* Apply prettier
* Apply prettier
* Upgrade axios dependency
* Prettier
* Fix agent.id field in commands columns
---------
Co-authored-by: Luciano Gorza
Co-authored-by: Federico Rodriguez
---
.vscode/settings.json | 17 -
docker/osd-dev/dev.yml | 1 +
plugins/main/common/constants.ts | 5 +
plugins/main/opensearch_dashboards.json | 3 +-
plugins/main/public/app-router.tsx | 50 ++-
.../fleet-commands-data-source-repository.ts | 38 +++
.../commands/fleet-commands-data-source.ts | 24 ++
.../pattern/fleet/commands/index.ts | 2 +
.../fleet/fleet-data-source-repository.ts | 38 +++
.../pattern/fleet/fleet-data-source.ts | 24 ++
.../fleet-groups-data-source-repository.ts | 38 +++
.../fleet/groups/fleet-groups-data-source.ts | 24 ++
.../data-source/pattern/fleet/groups/index.ts | 2 +
.../common/data-source/pattern/fleet/index.ts | 4 +
.../common/data-source/pattern/index.ts | 3 +-
.../common/search-bar/search-bar.tsx | 2 +-
.../public/components/common/tables/index.ts | 1 +
.../common/tables/table-indexer.tsx | 293 ++++++++++++++++++
plugins/main/public/kibana-services.ts | 3 +
plugins/main/public/plugin.ts | 2 +
.../redux/reducers/appConfigReducers.ts | 1 +
plugins/main/public/types.ts | 2 +
plugins/main/public/utils/applications.ts | 24 +-
plugins/wazuh-fleet/.i18nrc.json | 7 +
plugins/wazuh-fleet/README.md | 27 ++
plugins/wazuh-fleet/common/constants.ts | 2 +
plugins/wazuh-fleet/common/types.ts | 17 +
.../wazuh-fleet/opensearch_dashboards.json | 16 +
plugins/wazuh-fleet/package.json | 29 ++
.../agents/details/dashboard/dashboard.scss | 7 +
.../agents/details/dashboard/dashboard.tsx | 28 ++
.../details/dashboard/dashboard_panels.ts | 137 ++++++++
.../agents/details/dashboard/events-count.tsx | 66 ++++
.../agents/details/dashboard/index.tsx | 1 +
.../components/agents/details/details.tsx | 284 +++++++++++++++++
.../components/agents/details/index.tsx | 1 +
.../agents/details/networks/index.tsx | 1 +
.../agents/details/networks/networks.tsx | 9 +
.../components/agents/details/resume.tsx | 84 +++++
.../public/components/agents/index.tsx | 1 +
.../agents/list/actions/actions.tsx | 90 ++++++
.../public/components/agents/list/columns.tsx | 153 +++++++++
.../public/components/agents/list/index.tsx | 1 +
.../public/components/agents/list/list.tsx | 154 +++++++++
.../components/agents/list/visualizations.tsx | 191 ++++++++++++
.../public/components/commands/index.ts | 1 +
.../components/commands/list/columns.tsx | 159 ++++++++++
.../public/components/commands/list/index.tsx | 1 +
.../public/components/commands/list/list.tsx | 59 ++++
.../public/components/common/agent-groups.tsx | 18 ++
.../public/components/common/host-os.tsx | 33 ++
.../public/components/common/index.tsx | 2 +
.../public/components/fleet-management.tsx | 134 ++++++++
.../public/components/groups/index.tsx | 1 +
.../groups/list/actions/actions.tsx | 37 +++
.../public/components/groups/list/columns.tsx | 70 +++++
.../public/components/groups/list/list.tsx | 85 +++++
.../wazuh-fleet/public/components/index.tsx | 1 +
plugins/wazuh-fleet/public/index.ts | 8 +
plugins/wazuh-fleet/public/plugin-services.ts | 10 +
plugins/wazuh-fleet/public/plugin.ts | 31 ++
plugins/wazuh-fleet/public/types.ts | 14 +
plugins/wazuh-fleet/scripts/jest.js | 22 ++
plugins/wazuh-fleet/scripts/manifest.js | 16 +
plugins/wazuh-fleet/scripts/runner.js | 148 +++++++++
plugins/wazuh-fleet/server/index.ts | 11 +
plugins/wazuh-fleet/server/plugin-services.ts | 14 +
plugins/wazuh-fleet/server/plugin.ts | 74 +++++
plugins/wazuh-fleet/server/types.ts | 18 ++
plugins/wazuh-fleet/test/jest/config.js | 41 +++
plugins/wazuh-fleet/translations/en-US.json | 79 +++++
plugins/wazuh-fleet/tsconfig.json | 17 +
plugins/wazuh-fleet/yarn.lock | 98 ++++++
.../DIS_Settings.json | 7 +
.../DIS_Template.json | 99 ++++++
.../DIS_Template_Legacy.json | 104 +++++++
.../dataInjectScript.py | 290 +++++++++++++++++
.../wazuh-fleet-commands-generator/readme.md | 18 ++
.../requirements.txt | 1 +
.../wazuh-fleet-generator/DIS_Settings.json | 11 +
.../DIS_TemplateFleetAgents.json | 92 ++++++
.../DIS_TemplateInventoryNetworks.json | 89 ++++++
.../DIS_TemplateInventorySystem.json | 120 +++++++
.../wazuh-fleet-generator/dataInjectScript.py | 293 ++++++++++++++++++
scripts/wazuh-fleet-generator/readme.md | 18 ++
.../wazuh-fleet-generator/requirements.txt | 1 +
.../DIS_Settings.json | 7 +
.../DIS_Template.json | 69 +++++
.../dataInjectScript.py | 186 +++++++++++
.../wazuh-fleet-groups-generator/readme.md | 18 ++
.../requirements.txt | 1 +
91 files changed, 4510 insertions(+), 23 deletions(-)
delete mode 100644 .vscode/settings.json
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/commands/fleet-commands-data-source-repository.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/commands/fleet-commands-data-source.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/commands/index.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/fleet-data-source-repository.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/fleet-data-source.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/groups/fleet-groups-data-source-repository.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/groups/fleet-groups-data-source.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/groups/index.ts
create mode 100644 plugins/main/public/components/common/data-source/pattern/fleet/index.ts
create mode 100644 plugins/main/public/components/common/tables/table-indexer.tsx
create mode 100644 plugins/wazuh-fleet/.i18nrc.json
create mode 100755 plugins/wazuh-fleet/README.md
create mode 100644 plugins/wazuh-fleet/common/constants.ts
create mode 100644 plugins/wazuh-fleet/common/types.ts
create mode 100644 plugins/wazuh-fleet/opensearch_dashboards.json
create mode 100644 plugins/wazuh-fleet/package.json
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.scss
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard_panels.ts
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/dashboard/events-count.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/dashboard/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/details.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/networks/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/networks/networks.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/details/resume.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/list/actions/actions.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/list/columns.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/list/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/list/list.tsx
create mode 100644 plugins/wazuh-fleet/public/components/agents/list/visualizations.tsx
create mode 100644 plugins/wazuh-fleet/public/components/commands/index.ts
create mode 100644 plugins/wazuh-fleet/public/components/commands/list/columns.tsx
create mode 100644 plugins/wazuh-fleet/public/components/commands/list/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/commands/list/list.tsx
create mode 100644 plugins/wazuh-fleet/public/components/common/agent-groups.tsx
create mode 100644 plugins/wazuh-fleet/public/components/common/host-os.tsx
create mode 100644 plugins/wazuh-fleet/public/components/common/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/fleet-management.tsx
create mode 100644 plugins/wazuh-fleet/public/components/groups/index.tsx
create mode 100644 plugins/wazuh-fleet/public/components/groups/list/actions/actions.tsx
create mode 100644 plugins/wazuh-fleet/public/components/groups/list/columns.tsx
create mode 100644 plugins/wazuh-fleet/public/components/groups/list/list.tsx
create mode 100644 plugins/wazuh-fleet/public/components/index.tsx
create mode 100644 plugins/wazuh-fleet/public/index.ts
create mode 100644 plugins/wazuh-fleet/public/plugin-services.ts
create mode 100644 plugins/wazuh-fleet/public/plugin.ts
create mode 100644 plugins/wazuh-fleet/public/types.ts
create mode 100644 plugins/wazuh-fleet/scripts/jest.js
create mode 100644 plugins/wazuh-fleet/scripts/manifest.js
create mode 100755 plugins/wazuh-fleet/scripts/runner.js
create mode 100644 plugins/wazuh-fleet/server/index.ts
create mode 100644 plugins/wazuh-fleet/server/plugin-services.ts
create mode 100644 plugins/wazuh-fleet/server/plugin.ts
create mode 100644 plugins/wazuh-fleet/server/types.ts
create mode 100644 plugins/wazuh-fleet/test/jest/config.js
create mode 100644 plugins/wazuh-fleet/translations/en-US.json
create mode 100644 plugins/wazuh-fleet/tsconfig.json
create mode 100644 plugins/wazuh-fleet/yarn.lock
create mode 100644 scripts/wazuh-fleet-commands-generator/DIS_Settings.json
create mode 100644 scripts/wazuh-fleet-commands-generator/DIS_Template.json
create mode 100644 scripts/wazuh-fleet-commands-generator/DIS_Template_Legacy.json
create mode 100644 scripts/wazuh-fleet-commands-generator/dataInjectScript.py
create mode 100644 scripts/wazuh-fleet-commands-generator/readme.md
create mode 100644 scripts/wazuh-fleet-commands-generator/requirements.txt
create mode 100644 scripts/wazuh-fleet-generator/DIS_Settings.json
create mode 100644 scripts/wazuh-fleet-generator/DIS_TemplateFleetAgents.json
create mode 100644 scripts/wazuh-fleet-generator/DIS_TemplateInventoryNetworks.json
create mode 100644 scripts/wazuh-fleet-generator/DIS_TemplateInventorySystem.json
create mode 100644 scripts/wazuh-fleet-generator/dataInjectScript.py
create mode 100644 scripts/wazuh-fleet-generator/readme.md
create mode 100644 scripts/wazuh-fleet-generator/requirements.txt
create mode 100644 scripts/wazuh-fleet-groups-generator/DIS_Settings.json
create mode 100644 scripts/wazuh-fleet-groups-generator/DIS_Template.json
create mode 100644 scripts/wazuh-fleet-groups-generator/dataInjectScript.py
create mode 100644 scripts/wazuh-fleet-groups-generator/readme.md
create mode 100644 scripts/wazuh-fleet-groups-generator/requirements.txt
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index b96fcd2154..0000000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "diffEditor.ignoreTrimWhitespace": true,
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "editor.formatOnPaste": true,
- "editor.formatOnSave": true,
- "editor.inlineSuggest.enabled": true,
- "editor.insertSpaces": true,
- "editor.minimap.enabled": true,
- "editor.rulers": [80, 100],
- "editor.tabSize": 2,
- "editor.trimAutoWhitespace": true,
- "editor.wordWrap": "on",
- "explorer.confirmDelete": true,
- "files.autoSave": "off",
- "javascript.updateImportsOnFileMove.enabled": "always",
- "typescript.updateImportsOnFileMove.enabled": "always"
-}
diff --git a/docker/osd-dev/dev.yml b/docker/osd-dev/dev.yml
index 24d9c2821e..c52f908135 100755
--- a/docker/osd-dev/dev.yml
+++ b/docker/osd-dev/dev.yml
@@ -245,6 +245,7 @@ services:
- '${SRC}/main:/home/node/kbn/plugins/wazuh'
- '${SRC}/wazuh-core:/home/node/kbn/plugins/wazuh-core'
- '${SRC}/wazuh-check-updates:/home/node/kbn/plugins/wazuh-check-updates'
+ - '${SRC}/wazuh-fleet:/home/node/kbn/plugins/wazuh-fleet'
- wd_certs:/home/node/kbn/certs/
- ${WAZUH_DASHBOARD_CONF}:/home/node/kbn/config/opensearch_dashboards.yml
- ./config/${OSD_MAJOR}/osd/wazuh.yml:/home/node/kbn/data/wazuh/config/wazuh.yml
diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts
index 26fc95c5c4..7729cb5e14 100644
--- a/plugins/main/common/constants.ts
+++ b/plugins/main/common/constants.ts
@@ -51,6 +51,11 @@ export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities-*';
export const WAZUH_INDEX_TYPE_VULNERABILITIES = 'vulnerabilities';
export const VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER = 'wazuh.cluster.name';
+// Wazuh Fleet
+export const WAZUH_FLEET_PATTERN = 'wazuh-fleet-*';
+export const WAZUH_INDEX_TYPE_FLEET = 'fleet';
+export const FLEET_IMPLICIT_CLUSTER_MODE_FILTER = 'wazuh.cluster.name';
+
// Job - Wazuh initialize
export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana';
diff --git a/plugins/main/opensearch_dashboards.json b/plugins/main/opensearch_dashboards.json
index 115e01bfa0..457a87cd60 100644
--- a/plugins/main/opensearch_dashboards.json
+++ b/plugins/main/opensearch_dashboards.json
@@ -18,7 +18,8 @@
"opensearchDashboardsUtils",
"opensearchDashboardsLegacy",
"wazuhCheckUpdates",
- "wazuhCore"
+ "wazuhCore",
+ "wazuhFleet"
],
"optionalPlugins": [
"security",
diff --git a/plugins/main/public/app-router.tsx b/plugins/main/public/app-router.tsx
index 842c07ae03..3bbf4e224a 100644
--- a/plugins/main/public/app-router.tsx
+++ b/plugins/main/public/app-router.tsx
@@ -1,7 +1,11 @@
import React, { useEffect } from 'react';
import { Router, Route, Switch, Redirect } from 'react-router-dom';
import { ToolsRouter } from './components/tools/tools-router';
-import { getWazuhCorePlugin, getWzMainParams } from './kibana-services';
+import {
+ getWazuhCorePlugin,
+ getWazuhFleetPlugin,
+ getWzMainParams,
+} from './kibana-services';
import { updateCurrentPlatform } from './redux/actions/appStateActions';
import { useDispatch } from 'react-redux';
import { checkPluginVersion } from './utils';
@@ -21,12 +25,31 @@ import { Settings } from './components/settings';
import { WzSecurity } from './components/security';
import $ from 'jquery';
import NavigationService from './react-services/navigation-service';
+import {
+ FleetDataSource,
+ FleetDataSourceRepository,
+ useDataSource,
+ FleetGroupsDataSource,
+ FleetGroupsDataSourceRepository,
+ FleetCommandsDataSource,
+ FleetCommandsDataSourceRepository,
+ AlertsDataSource,
+ AlertsDataSourceRepository,
+} from './components/common/data-source';
+import useSearchBar from './components/common/search-bar/use-search-bar';
+import { WzSearchBar } from './components/common/search-bar';
+import { TableIndexer } from './components/common/tables';
+import DocDetails from './components/common/wazuh-discover/components/doc-details';
+import { useTimeFilter } from './components/common/hooks';
+import { LoadingSpinner } from './components/common/loading-spinner/loading-spinner';
export function Application(props) {
const dispatch = useDispatch();
const navigationService = NavigationService.getInstance();
const history = navigationService.getHistory();
+ const { FleetManagement } = getWazuhFleetPlugin();
+
useEffect(() => {
// Get the dashboard security
getWazuhCorePlugin()
@@ -78,6 +101,31 @@ export function Application(props) {
exact
render={MainEndpointsSummary}
>
+ (
+
+ )}
+ >
TDocumentDetailsTab[]);
+ tableSortingInitialField?: string;
+ tableSortingInitialDirection?: string;
+ topTableComponent?: React.ReactNode;
+ tableProps?: any;
+}) => {
+ const {
+ dataSource,
+ filters,
+ fetchFilters,
+ isLoading: isDataSourceLoading,
+ fetchData,
+ setFilters,
+ } = useDataSource({
+ DataSource: DataSource,
+ repository: new DataSourceRepository(),
+ });
+ const [pagination, setPagination] = useState({
+ pageIndex: 0,
+ pageSize: 15,
+ });
+
+ const [sorting, setSorting] = useState({
+ sort: {
+ field: tableSortingInitialField,
+ direction: tableSortingInitialDirection,
+ },
+ });
+ 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 details';
+ return (
+
+ onClickInspectDoc(rowIndex)}
+ iconType='inspect'
+ aria-label={inspectHintMsg}
+ />
+
+ );
+ };
+
+ // const dataGridProps = useDataGrid({
+ // ariaLabelledBy: 'Table',
+ // defaultColumns: defaultColumns,
+ // 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: {
+ columns: [
+ {
+ id: sorting.sort.field,
+ direction: sorting.sort.direction,
+ },
+ ],
+ },
+ })
+ .then(results => {
+ setResults(results);
+ })
+ .catch(error => {
+ const searchError = ErrorFactory.create(HttpError, {
+ error,
+ message: 'Error fetching data',
+ });
+ ErrorHandler.handleError(searchError);
+ });
+ }, [
+ JSON.stringify(fetchFilters),
+ JSON.stringify(query),
+ JSON.stringify(pagination),
+ JSON.stringify(sorting),
+ ]);
+
+ function tableOnChange({ page = {}, sort = {} }) {
+ const { index: pageIndex, size: pageSize } = page;
+ const { field, direction } = sort;
+ setPagination({
+ pageIndex,
+ pageSize,
+ });
+ setSorting({
+ sort: {
+ field,
+ direction,
+ },
+ });
+ }
+
+ const tablePagination = {
+ ...pagination,
+ totalItemCount: results?.hits?.total,
+ pageSizeOptions: [15, 25, 50, 100],
+ hidePerPageOptions: false,
+ };
+
+ const getRowProps = item => {
+ return {
+ 'data-test-subj': `row-${item.id}`,
+ onClick: () => {
+ setInspectedHit(
+ results.hits.hits.find(({ _source }) => _source.id === item.id),
+ );
+ },
+ };
+ };
+ return (
+
+
+
+ {isDataSourceLoading ? (
+
+ ) : (
+
+ )}
+
+ {topTableComponent}
+
+ ({ ...rest }))}
+ items={results?.hits?.hits?.map(({ _source }) => _source) || []}
+ loading={isDataSourceLoading}
+ pagination={tablePagination}
+ sorting={sorting}
+ onChange={tableOnChange}
+ // rowProps={getRowProps}
+ {...tableProps}
+ />
+
+ {inspectedHit && (
+ setInspectedHit(undefined)} size='m'>
+
+
+ Details
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/plugins/main/public/kibana-services.ts b/plugins/main/public/kibana-services.ts
index 1c536dc5e1..e70a5859a6 100644
--- a/plugins/main/public/kibana-services.ts
+++ b/plugins/main/public/kibana-services.ts
@@ -15,6 +15,7 @@ import { VisualizationsStart } from '../../../src/plugins/visualizations/public'
import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
import { AppPluginStartDependencies } from './types';
import { WazuhCheckUpdatesPluginStart } from '../../wazuh-check-updates/public';
+import { WazuhFleetPluginStart } from '../../wazuh-fleet/public';
let angularModule: any = null;
let discoverModule: any = null;
@@ -51,6 +52,8 @@ export const [getHeaderActionMenuMounter, setHeaderActionMenuMounter] =
createGetterSetter(
'headerActionMenuMounter',
);
+export const [getWazuhFleetPlugin, setWazuhFleetPlugin] =
+ createGetterSetter('WazuhFleetPlugin');
/**
* set bootstrapped inner angular module
diff --git a/plugins/main/public/plugin.ts b/plugins/main/public/plugin.ts
index 872c54eab7..316b92fbbd 100644
--- a/plugins/main/public/plugin.ts
+++ b/plugins/main/public/plugin.ts
@@ -24,6 +24,7 @@ import {
setWazuhCheckUpdatesPlugin,
setHeaderActionMenuMounter,
setWazuhCorePlugin,
+ setWazuhFleetPlugin,
} from './kibana-services';
import { validate as validateNodeCronInterval } from 'node-cron';
import {
@@ -210,6 +211,7 @@ export class WazuhPlugin
setErrorOrchestrator(ErrorOrchestratorService);
setWazuhCheckUpdatesPlugin(plugins.wazuhCheckUpdates);
setWazuhCorePlugin(plugins.wazuhCore);
+ setWazuhFleetPlugin(plugins.wazuhFleet);
return {};
}
}
diff --git a/plugins/main/public/redux/reducers/appConfigReducers.ts b/plugins/main/public/redux/reducers/appConfigReducers.ts
index 742a99f216..e7501d98c1 100644
--- a/plugins/main/public/redux/reducers/appConfigReducers.ts
+++ b/plugins/main/public/redux/reducers/appConfigReducers.ts
@@ -20,6 +20,7 @@ const initialState: AppConfigState = {
data: {
// TODO: this should use the configuration service
'vulnerabilities.pattern': 'wazuh-states-vulnerabilities',
+ 'fleet.pattern': 'wazuh-fleet-agents',
},
};
diff --git a/plugins/main/public/types.ts b/plugins/main/public/types.ts
index 9d3c0e7915..a12d598b93 100644
--- a/plugins/main/public/types.ts
+++ b/plugins/main/public/types.ts
@@ -20,6 +20,7 @@ import {
import { WazuhCheckUpdatesPluginStart } from '../../wazuh-check-updates/public';
import { WazuhCorePluginStart } from '../../wazuh-core/public';
import { DashboardStart } from '../../../src/plugins/dashboard/public';
+import { WazuhFleetPluginStart } from '../../wazuh-fleet/public';
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
@@ -33,6 +34,7 @@ export interface AppPluginStartDependencies {
wazuhCheckUpdates: WazuhCheckUpdatesPluginStart;
wazuhCore: WazuhCorePluginStart;
dashboard: DashboardStart;
+ wazuhFleet: WazuhFleetPluginStart;
}
export interface AppDependencies {
core: CoreStart;
diff --git a/plugins/main/public/utils/applications.ts b/plugins/main/public/utils/applications.ts
index 46331558ea..7fce7ee21d 100644
--- a/plugins/main/public/utils/applications.ts
+++ b/plugins/main/public/utils/applications.ts
@@ -82,6 +82,25 @@ export const fileIntegrityMonitoring = {
}`,
};
+export const fleetManagement = {
+ category: 'wz-category-server-management',
+ id: 'fleet-management',
+ title: i18n.translate('wz-app-fleet-management-title', {
+ defaultMessage: 'Fleet Management',
+ }),
+ breadcrumbLabel: i18n.translate('wz-app-fleet-management-breadcrumbLabel', {
+ defaultMessage: 'Fleet Management',
+ }),
+ description: i18n.translate('wz-app-fleet-management-description', {
+ defaultMessage: 'Fleet Management.',
+ }),
+ euiIconType: 'spacesApp',
+ order: 600,
+ showInOverviewApp: false,
+ showInAgentMenu: false,
+ redirectTo: () => '/fleet-management/',
+};
+
export const endpointSummary = {
category: 'wz-category-server-management',
id: 'endpoints-summary',
@@ -855,11 +874,12 @@ export const Applications = [
github,
office365,
docker,
- endpointSummary,
+ // endpointSummary,
+ fleetManagement,
rules,
decoders,
cdbLists,
- endpointGroups,
+ // endpointGroups,
serverStatus,
cluster,
statistics,
diff --git a/plugins/wazuh-fleet/.i18nrc.json b/plugins/wazuh-fleet/.i18nrc.json
new file mode 100644
index 0000000000..cd6b285378
--- /dev/null
+++ b/plugins/wazuh-fleet/.i18nrc.json
@@ -0,0 +1,7 @@
+{
+ "prefix": "wazuhCheckUpdates",
+ "paths": {
+ "wazuhCheckUpdates": "."
+ },
+ "translations": ["translations/en-US.json"]
+}
diff --git a/plugins/wazuh-fleet/README.md b/plugins/wazuh-fleet/README.md
new file mode 100755
index 0000000000..7f1c36b15a
--- /dev/null
+++ b/plugins/wazuh-fleet/README.md
@@ -0,0 +1,27 @@
+# Wazuh Fleet Plugin
+
+**Wazuh Fleet Plugin** is an extension for Wazuh...
+
+## Features
+
+### Feature 1
+
+### Feature 2.
+
+## Use cases
+
+### Use case 1
+
+### Use case 2
+
+## Data Storage
+
+## Software and libraries used
+
+- [OpenSearch](https://opensearch.org/)
+
+- [Elastic UI Framework](https://eui.elastic.co/)
+
+- [Node.js](https://nodejs.org)
+
+- [React](https://reactjs.org)
diff --git a/plugins/wazuh-fleet/common/constants.ts b/plugins/wazuh-fleet/common/constants.ts
new file mode 100644
index 0000000000..4bcc9500f4
--- /dev/null
+++ b/plugins/wazuh-fleet/common/constants.ts
@@ -0,0 +1,2 @@
+export const PLUGIN_ID = 'wazuhFleet';
+export const PLUGIN_NAME = 'wazuh_fleet';
diff --git a/plugins/wazuh-fleet/common/types.ts b/plugins/wazuh-fleet/common/types.ts
new file mode 100644
index 0000000000..cad8501836
--- /dev/null
+++ b/plugins/wazuh-fleet/common/types.ts
@@ -0,0 +1,17 @@
+export type Agent = {
+ agent: {
+ id: string;
+ name: string;
+ version: string;
+ };
+ host: {
+ os: {
+ full: string;
+ name: string;
+ platform: string;
+ };
+ ip: string;
+ };
+ wazuh: {};
+ groups: string[];
+};
diff --git a/plugins/wazuh-fleet/opensearch_dashboards.json b/plugins/wazuh-fleet/opensearch_dashboards.json
new file mode 100644
index 0000000000..fea1d43559
--- /dev/null
+++ b/plugins/wazuh-fleet/opensearch_dashboards.json
@@ -0,0 +1,16 @@
+{
+ "id": "wazuhFleet",
+ "version": "5.0.0-00",
+ "opensearchDashboardsVersion": "opensearchDashboards",
+ "server": true,
+ "ui": true,
+ "requiredPlugins": [
+ "navigation",
+ "data",
+ "dashboard",
+ "embeddable",
+ "opensearchDashboardsUtils",
+ "wazuhCore"
+ ],
+ "optionalPlugins": ["securityDashboards"]
+}
diff --git a/plugins/wazuh-fleet/package.json b/plugins/wazuh-fleet/package.json
new file mode 100644
index 0000000000..48c44bc288
--- /dev/null
+++ b/plugins/wazuh-fleet/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "wazuh-fleet",
+ "version": "5.0.0",
+ "revision": "00",
+ "pluginPlatform": {
+ "version": "2.13.0"
+ },
+ "description": "Wazuh Fleet",
+ "private": true,
+ "scripts": {
+ "build": "yarn plugin-helpers build --opensearch-dashboards-version=$OPENSEARCH_DASHBOARDS_VERSION",
+ "plugin-helpers": "node ../../scripts/plugin_helpers",
+ "osd": "node ../../scripts/osd",
+ "test:ui:runner": "node ../../scripts/functional_test_runner.js",
+ "test:server": "plugin-helpers test:server",
+ "test:browser": "plugin-helpers test:browser",
+ "test:jest": "node scripts/jest --runInBand",
+ "test:jest:runner": "node scripts/runner test"
+ },
+ "dependencies": {
+ "axios": "^1.7.4",
+ "md5": "^2.3.0"
+ },
+ "devDependencies": {
+ "@testing-library/user-event": "^14.5.0",
+ "@types/": "testing-library/user-event",
+ "@types/md5": "^2.3.2"
+ }
+}
diff --git a/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.scss b/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.scss
new file mode 100644
index 0000000000..a950b57715
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.scss
@@ -0,0 +1,7 @@
+.wz-search-bar-no-padding .globalQueryBar:not(:empty) {
+ padding: 0px !important;
+}
+
+.wz-search-bar-no-padding .osdQueryBar {
+ justify-content: flex-start;
+}
diff --git a/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.tsx b/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.tsx
new file mode 100644
index 0000000000..99b2a35e59
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { getPlugins } from '../../../../plugin-services';
+import { EuiSpacer } from '@elastic/eui';
+import './dashboard.scss';
+import { EventsCount } from './events-count';
+
+export interface AgentDashboardProps {
+ agentId: string;
+}
+
+export const AgentDashboard = ({
+ agentId,
+ ...restProps
+}: AgentDashboardProps) => {
+ const SearchBar = getPlugins().data.ui.SearchBar;
+
+ return (
+
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard_panels.ts b/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard_panels.ts
new file mode 100644
index 0000000000..7d250e7d05
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/dashboard/dashboard_panels.ts
@@ -0,0 +1,137 @@
+import { DashboardPanelState } from '../../../../../../../src/plugins/dashboard/public/application';
+import { EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public';
+
+const getVisStateEventsCountEvolution = (indexPatternId: string) => ({
+ id: 'App-Agents-Welcome-Events-Evolution',
+ title: 'Events count evolution',
+ type: 'line',
+ params: {
+ type: 'line',
+ grid: { categoryLines: false },
+ categoryAxes: [
+ {
+ id: 'CategoryAxis-1',
+ type: 'category',
+ position: 'bottom',
+ show: true,
+ style: {},
+ scale: { type: 'linear' },
+ labels: { show: true, filter: true, truncate: 100 },
+ title: {},
+ },
+ ],
+ valueAxes: [
+ {
+ id: 'ValueAxis-1',
+ name: 'LeftAxis-1',
+ type: 'value',
+ position: 'left',
+ show: true,
+ style: {},
+ scale: { type: 'linear', mode: 'normal' },
+ labels: { show: true, rotate: 0, filter: false, truncate: 100 },
+ title: { text: 'Count' },
+ },
+ ],
+ seriesParams: [
+ {
+ show: true,
+ type: 'line',
+ mode: 'normal',
+ data: { label: 'Count', id: '1' },
+ valueAxis: 'ValueAxis-1',
+ drawLinesBetweenPoints: true,
+ lineWidth: 2,
+ interpolate: 'linear',
+ showCircles: true,
+ },
+ ],
+ addTooltip: true,
+ addLegend: false,
+ legendPosition: 'right',
+ times: [],
+ addTimeMarker: false,
+ labels: {},
+ thresholdLine: {
+ show: false,
+ value: 10,
+ width: 1,
+ style: 'full',
+ color: '#E7664C',
+ },
+ dimensions: {
+ x: null,
+ y: [
+ {
+ accessor: 0,
+ format: { id: 'number' },
+ params: {},
+ label: 'Count',
+ aggType: 'count',
+ },
+ ],
+ },
+ },
+ uiState: {
+ vis: { params: { sort: { columnIndex: 2, direction: 'desc' } } },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ { id: '1', enabled: true, type: 'count', schema: 'metric', params: {} },
+ {
+ id: '2',
+ enabled: true,
+ type: 'date_histogram',
+ schema: 'segment',
+ params: {
+ field: 'timestamp',
+ useNormalizedEsInterval: true,
+ scaleMetricValues: false,
+ interval: 'auto',
+ drop_partials: false,
+ min_doc_count: 1,
+ extended_bounds: {},
+ },
+ },
+ ],
+ },
+});
+
+export const getDashboardPanels = (
+ indexPatternId: string,
+): {
+ [panelId: string]: DashboardPanelState<
+ EmbeddableInput & { [k: string]: unknown }
+ >;
+} => {
+ return {
+ '1': {
+ gridData: {
+ w: 48,
+ h: 12,
+ x: 0,
+ y: 0,
+ i: '1',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '1',
+ savedVis: getVisStateEventsCountEvolution(indexPatternId),
+ },
+ },
+ };
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/details/dashboard/events-count.tsx b/plugins/wazuh-fleet/public/components/agents/details/dashboard/events-count.tsx
new file mode 100644
index 0000000000..8f3e928a7b
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/dashboard/events-count.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { getPlugins } from '../../../../plugin-services';
+import { getDashboardPanels } from './dashboard_panels';
+import { ViewMode } from '../../../../../../../src/plugins/embeddable/public';
+import {
+ EuiPanel,
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+
+export interface EventsCountProps {
+ AlertsDataSource: any;
+ AlertsDataSourceRepository: any;
+ useDataSource: any;
+ useTimeFilter: any;
+ LoadingSpinner: any;
+}
+export const EventsCount = ({
+ useDataSource,
+ AlertsDataSource,
+ AlertsDataSourceRepository,
+ useTimeFilter,
+ LoadingSpinner,
+}: EventsCountProps) => {
+ const {
+ dataSource,
+ fetchFilters,
+ isLoading: isDataSourceLoading,
+ } = useDataSource({
+ DataSource: AlertsDataSource,
+ repository: new AlertsDataSourceRepository(),
+ });
+
+ const plugins = getPlugins();
+ const DashboardByRenderer =
+ plugins.dashboard.DashboardContainerByValueRenderer;
+
+ const { timeFilter } = useTimeFilter();
+
+ return !isDataSourceLoading && dataSource ? (
+
+ ) : (
+
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/details/dashboard/index.tsx b/plugins/wazuh-fleet/public/components/agents/details/dashboard/index.tsx
new file mode 100644
index 0000000000..13c5f5265d
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/dashboard/index.tsx
@@ -0,0 +1 @@
+export { AgentDashboard } from './dashboard';
diff --git a/plugins/wazuh-fleet/public/components/agents/details/details.tsx b/plugins/wazuh-fleet/public/components/agents/details/details.tsx
new file mode 100644
index 0000000000..38468e4bd9
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/details.tsx
@@ -0,0 +1,284 @@
+import React, { useState, useEffect } from 'react';
+import { useParams } from 'react-router-dom';
+import {
+ EuiPageHeader,
+ EuiSpacer,
+ EuiButton,
+ EuiPopover,
+ EuiContextMenuPanel,
+ EuiContextMenuItem,
+ EuiHorizontalRule,
+ EuiTabbedContent,
+ EuiLoadingContent,
+ EuiContextMenu,
+ EuiIcon,
+} from '@elastic/eui';
+import { Agent } from '../../../../common/types';
+import { AgentResume } from './resume';
+import { AgentDashboard } from './dashboard';
+import { AgentNetworks } from './networks';
+
+export interface AgentDetailsProps {
+ useDataSource: any;
+ FleetDataSource: any;
+ FleetDataSourceRepository: any;
+}
+
+export const AgentDetails = ({
+ FleetDataSource,
+ FleetDataSourceRepository,
+ ...restProps
+}: AgentDetailsProps) => {
+ const { id } = useParams();
+
+ const {
+ dataSource,
+ isLoading: isDataSourceLoading,
+ fetchData,
+ filterManager,
+ fetchFilters,
+ } = restProps.useDataSource({
+ DataSource: FleetDataSource,
+ repository: new FleetDataSourceRepository(),
+ });
+
+ const [isAgentLoading, setIsAgentLoading] = useState(true);
+ const [agentData, setAgentData] = useState();
+ const [isActionsOpen, setIsActionsOpen] = useState(false);
+ const [isNavigateToOpen, setIsNavigateToOpen] = useState(false);
+
+ useEffect(() => {
+ if (!filterManager || isDataSourceLoading) return;
+
+ const filterByAgentId = filterManager.createFilter(
+ 'is',
+ 'agent.id',
+ id,
+ dataSource?.indexPattern.id,
+ );
+
+ fetchData({
+ filters: [filterByAgentId, ...fetchFilters],
+ })
+ .then((results: any) => {
+ setAgentData(results.hits.hits?.[0]?._source);
+ setIsAgentLoading(false);
+ })
+ .catch((error: any) => {
+ console.log(error);
+ });
+ }, [filterManager, isDataSourceLoading]);
+
+ if (isDataSourceLoading || isAgentLoading) {
+ return (
+
+
+
+ );
+ }
+
+ const closeNativagateTo = () => {
+ setIsNavigateToOpen(false);
+ };
+
+ const closeActions = () => {
+ setIsActionsOpen(false);
+ };
+
+ const navigateToPanels = [
+ {
+ id: 0,
+ items: [
+ {
+ name: 'Endpoint security',
+ icon: 'monitoringApp',
+ panel: 1,
+ },
+ {
+ name: 'Threat intelligencey',
+ icon: 'lensApp',
+ panel: 2,
+ },
+ ],
+ },
+ {
+ id: 1,
+ initialFocusedItemIndex: 1,
+ title: 'Endpoint security',
+ items: [
+ {
+ name: 'Configuration Assessment',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ {
+ name: 'Malware Detection',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ {
+ name: 'File Integrity Monitoring',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ ],
+ },
+ {
+ id: 2,
+ initialFocusedItemIndex: 1,
+ title: 'Threat intelligencey',
+ items: [
+ {
+ name: 'Threat Hunting',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ {
+ name: 'Vulnerability Detection',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ {
+ name: 'MITRE ATT&CT',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ {
+ name: 'VirusTotal',
+ onClick: () => {
+ closeNativagateTo();
+ },
+ },
+ ],
+ },
+ ];
+
+ const tabContent = (content: React.ReactNode) => (
+ <>
+
+ {content}
+ >
+ );
+
+ const tabs = [
+ {
+ id: 'dashboard',
+ name: 'Dashboard',
+ content: tabContent(),
+ },
+ {
+ id: 'networks',
+ name: 'Networks',
+ content: tabContent(),
+ },
+ {
+ id: 'processes',
+ name: 'Processes',
+ content: tabContent(Processes
),
+ },
+ {
+ id: 'packages',
+ name: 'Packages',
+ content: tabContent(Packages
),
+ },
+ {
+ id: 'configuration',
+ name: 'Configuration',
+ content: tabContent(Configuration
),
+ },
+ ];
+
+ return (
+ <>
+ setIsActionsOpen(!isActionsOpen)}
+ >
+ Actions
+
+ }
+ isOpen={isActionsOpen}
+ closePopover={closeActions}
+ panelPaddingSize='none'
+ anchorPosition='downLeft'
+ panelStyle={{ overflowY: 'unset' }}
+ >
+
+ Add groups to agent
+ ,
+
+ Remove groups from agent
+ ,
+ ,
+
+ Upgrade agent
+ ,
+
+ Upgrade tasks details
+ ,
+ ]}
+ />
+ ,
+ setIsNavigateToOpen(!isNavigateToOpen)}
+ >
+ Navigate to
+
+ }
+ isOpen={isNavigateToOpen}
+ closePopover={closeNativagateTo}
+ panelPaddingSize='none'
+ anchorPosition='downLeft'
+ panelStyle={{ overflowY: 'unset' }}
+ >
+
+ ,
+ ]}
+ />
+
+
+
+
+ >
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/details/index.tsx b/plugins/wazuh-fleet/public/components/agents/details/index.tsx
new file mode 100644
index 0000000000..f1bdb68026
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/index.tsx
@@ -0,0 +1 @@
+export { AgentDetails } from './details';
diff --git a/plugins/wazuh-fleet/public/components/agents/details/networks/index.tsx b/plugins/wazuh-fleet/public/components/agents/details/networks/index.tsx
new file mode 100644
index 0000000000..00685a2675
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/networks/index.tsx
@@ -0,0 +1 @@
+export { AgentNetworks } from './networks';
diff --git a/plugins/wazuh-fleet/public/components/agents/details/networks/networks.tsx b/plugins/wazuh-fleet/public/components/agents/details/networks/networks.tsx
new file mode 100644
index 0000000000..26647a2747
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/networks/networks.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+
+export interface AgentNetworksProps {
+ agentId: string;
+}
+
+export const AgentNetworks = ({ agentId }: AgentNetworksProps) => {
+ return Networks {agentId}
;
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/details/resume.tsx b/plugins/wazuh-fleet/public/components/agents/details/resume.tsx
new file mode 100644
index 0000000000..9d0a1642e8
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/details/resume.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import {
+ EuiDescriptionList,
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiDescriptionListTitle,
+ EuiDescriptionListDescription,
+ EuiCard,
+} from '@elastic/eui';
+import { Agent } from '../../../../common/types';
+import { AgentGroups, HostOS } from '../../common';
+import { getWazuhCore } from '../../../plugin-services';
+
+export interface AgentResumeProps {
+ agent: Agent;
+}
+export const AgentResume = ({ agent }: AgentResumeProps) => {
+ const { utils } = getWazuhCore();
+
+ return (
+
+
+
+
+
+
+ Groups
+
+
+
+ Cluster node
+
+ {agent.wazuh.cluster.name}
+
+
+
+
+
+ Version
+
+ {agent.agent.version}
+
+ Last login
+
+ {utils.formatUIDate(agent.agent.last_login)}
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+ {agent.host.hostname}
+
+ IP
+
+ {agent.host.ip}
+
+
+
+
+
+ OS
+
+
+
+ Architecture
+
+ {agent.host.architecture}
+
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/index.tsx b/plugins/wazuh-fleet/public/components/agents/index.tsx
new file mode 100644
index 0000000000..5e5c413145
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/index.tsx
@@ -0,0 +1 @@
+export { AgentList } from './list';
diff --git a/plugins/wazuh-fleet/public/components/agents/list/actions/actions.tsx b/plugins/wazuh-fleet/public/components/agents/list/actions/actions.tsx
new file mode 100644
index 0000000000..b069ea20c5
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/list/actions/actions.tsx
@@ -0,0 +1,90 @@
+// import React from 'react';
+// import { EuiToolTip } from '@elastic/eui';
+// import { endpointSummary } from '../../../../utils/applications';
+// import { API_NAME_AGENT_STATUS } from '../../../../../common/constants';
+// import { WzElementPermissions } from '../../../common/permissions/element';
+// import { Agent } from '../../types';
+// import NavigationService from '../../../../react-services/navigation-service';
+
+import { Agent } from '../../../../../common/types';
+
+export const agentsTableActions = ({
+ setIsFlyoutAgentVisible,
+ setAgent,
+}: {
+ setIsFlyoutAgentVisible: (isVisible: boolean) => void;
+ setAgent: (agent: Agent) => void;
+}) =>
+ // allowEditGroups: boolean,
+ // allowUpgrade: boolean,
+ // setAgent: (agent: Agent) => void,
+ // setIsEditGroupsVisible: (visible: boolean) => void,
+ // setIsUpgradeModalVisible: (visible: boolean) => void,
+ // outdatedAgents: Agent[],
+ [
+ {
+ name: 'View agent details',
+ description: 'View agent details',
+ icon: 'eye',
+ type: 'icon',
+ isPrimary: true,
+ color: 'primary',
+ onClick: (agent: Agent) => {
+ setAgent(agent);
+ setIsFlyoutAgentVisible(true);
+ },
+ // enabled: agent => agent.status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED,
+ // onClick: agent =>
+ // NavigationService.getInstance().navigateToApp(endpointSummary.id, {
+ // path: `#/agents?tab=welcome&agent=${agent.id}`,
+ // }),
+ },
+ {
+ name: 'Agent configuration',
+ description: 'Agent configuration',
+ icon: 'wrench',
+ type: 'icon',
+ onClick: () => {},
+ // onClick: agent =>
+ // NavigationService.getInstance().navigateToApp(endpointSummary.id, {
+ // path: `#/agents?tab=configuration&agent=${agent.id}`,
+ // }),
+ // enabled: agent => agent.status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED,
+ // 'data-test-subj': 'action-configuration',
+ },
+ {
+ name: 'Edit groups',
+ description: 'Edit groups',
+ icon: 'pencil',
+ type: 'icon',
+ onClick: () => {},
+ // onClick: (agent: Agent) => {
+ // setAgent(agent);
+ // setIsEditGroupsVisible(true);
+ // },
+ // 'data-test-subj': 'action-groups',
+ // enabled: () => allowEditGroups,
+ },
+ {
+ name: 'Upgrade',
+ description: 'Upgrade',
+ icon: 'package',
+ type: 'icon',
+ onClick: () => {},
+ // onClick: agent => {
+ // setAgent(agent);
+ // setIsUpgradeModalVisible(true);
+ // },
+ // 'data-test-subj': 'action-upgrade',
+ // enabled: agent => {
+ // const isOutdated = !!outdatedAgents.find(
+ // outdatedAgent => outdatedAgent.id === agent.id,
+ // );
+ // return (
+ // allowUpgrade &&
+ // agent.status === API_NAME_AGENT_STATUS.ACTIVE &&
+ // isOutdated
+ // );
+ // },
+ },
+ ];
diff --git a/plugins/wazuh-fleet/public/components/agents/list/columns.tsx b/plugins/wazuh-fleet/public/components/agents/list/columns.tsx
new file mode 100644
index 0000000000..7257ba5ffa
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/list/columns.tsx
@@ -0,0 +1,153 @@
+import React from 'react';
+import { agentsTableActions } from './actions/actions';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHealth,
+ EuiToolTip,
+ EuiText,
+ EuiLink,
+} from '@elastic/eui';
+import { getCore, getWazuhCore } from '../../../plugin-services';
+import { Agent } from '../../../../common/types';
+import { AgentGroups, HostOS } from '../../common';
+
+export const agentsTableColumns = ({
+ setIsFlyoutAgentVisible,
+ setAgent,
+}: {
+ setIsFlyoutAgentVisible: (isVisible: boolean) => void;
+ setAgent: (agent: Agent) => void;
+ // allowEditGroups: boolean,
+ // allowUpgrade: boolean,
+ // setAgent: (agents: Agent) => void,
+ // setIsEditGroupsVisible: (visible: boolean) => void,
+ // setIsUpgradeModalVisible: (visible: boolean) => void,
+ // setFilters: (filters) => void,
+ // outdatedAgents: Agent[],
+}) => [
+ {
+ field: 'agent.name',
+ name: 'Name / ID',
+ sortable: true,
+ show: true,
+ searchable: true,
+ render: (field: string, agentData: Agent) => (
+
+
+
+ {agentData.agent.name}
+
+
+
+
+
+ {`${agentData.agent.id.substring(0, 14)}...`}
+
+
+
+
+ ),
+ },
+ {
+ field: 'agent.groups',
+ name: 'Groups',
+ sortable: true,
+ show: true,
+ render: (groups: string[]) => ,
+ searchable: true,
+ },
+ {
+ field: 'wazuh.cluster.name',
+ name: 'Cluster node',
+ sortable: true,
+ show: true,
+ searchable: true,
+ width: '140px',
+ },
+ {
+ field: 'agent.version',
+ name: 'Version',
+ sortable: true,
+ show: true,
+ searchable: true,
+ width: '100px',
+ render: (version: string, agent: any) => {
+ const isOutdated = false;
+ // const isOutdated = !!outdatedAgents.find(
+ // outdatedAgent => outdatedAgent.id === agent.id,
+ // );
+ return (
+
+ {version}
+ {isOutdated ? (
+
+ Outdated
}>
+
+
+
+ ) : null}
+
+ );
+ },
+ },
+ {
+ field: 'host.os.name,host.os.version',
+ composeField: ['host.os.name', 'host.os.version'],
+ name: 'Host OS',
+ sortable: true,
+ show: true,
+ render: (field: string, agentData: Agent) => (
+
+ ),
+ searchable: true,
+ },
+ {
+ field: 'host.ip',
+ name: 'Host IP',
+ sortable: true,
+ show: true,
+ searchable: true,
+ width: '140px',
+ },
+ // {
+ // field: 'status',
+ // name: 'Status',
+ // truncateText: true,
+ // sortable: true,
+ // show: true,
+ // render: (status, agent) => ,
+ // },
+ {
+ field: 'agent.last_login',
+ name: 'Last login',
+ render: (last_login: Date) => {
+ const { utils } = getWazuhCore();
+ return utils.formatUIDate(last_login);
+ },
+ sortable: true,
+ show: false,
+ searchable: false,
+ },
+ {
+ field: 'actions',
+ name: 'Actions',
+ show: true,
+ actions: agentsTableActions({ setIsFlyoutAgentVisible, setAgent }),
+ // allowEditGroups,
+ // allowUpgrade,
+ // setAgent,
+ // setIsEditGroupsVisible,
+ // setIsUpgradeModalVisible,
+ // outdatedAgents,
+ },
+];
diff --git a/plugins/wazuh-fleet/public/components/agents/list/index.tsx b/plugins/wazuh-fleet/public/components/agents/list/index.tsx
new file mode 100644
index 0000000000..5e5c413145
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/list/index.tsx
@@ -0,0 +1 @@
+export { AgentList } from './list';
diff --git a/plugins/wazuh-fleet/public/components/agents/list/list.tsx b/plugins/wazuh-fleet/public/components/agents/list/list.tsx
new file mode 100644
index 0000000000..8a8b957c6b
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/list/list.tsx
@@ -0,0 +1,154 @@
+import React, { useState } from 'react';
+import {
+ EuiPageHeader,
+ EuiSpacer,
+ EuiButton,
+ EuiPopover,
+ EuiContextMenuPanel,
+ EuiContextMenuItem,
+ EuiHorizontalRule,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiText,
+ EuiTitle,
+ EuiLink,
+} from '@elastic/eui';
+import { agentsTableColumns } from './columns';
+import { AgentsVisualizations } from './visualizations';
+import { Agent } from '../../../../common/types';
+import { AgentResume } from '../details/resume';
+import { getCore } from '../../../plugin-services';
+
+export interface AgentListProps {
+ FleetDataSource: any;
+ FleetDataSourceRepository: any;
+ TableIndexer: any;
+}
+
+export const AgentList = ({
+ FleetDataSource,
+ FleetDataSourceRepository,
+ TableIndexer,
+}: AgentListProps) => {
+ const [isActionsOpen, setIsActionsOpen] = useState(false);
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const [agent, setAgent] = useState();
+
+ const closeActions = () => {
+ setIsActionsOpen(false);
+ };
+
+ const handleOnOpenAgentDetails = (agentId: string) => {};
+
+ return (
+ <>
+
+ Deploy new agent
+ ,
+ setIsActionsOpen(!isActionsOpen)}
+ >
+ Actions
+
+ }
+ isOpen={isActionsOpen}
+ closePopover={closeActions}
+ panelPaddingSize='none'
+ anchorPosition='downLeft'
+ panelStyle={{ overflowY: 'unset' }}
+ >
+
+ Add groups to agents
+ ,
+
+ Remove groups from agents
+ ,
+ ,
+
+ Upgrade agents
+ ,
+
+ Upgrade tasks details
+ ,
+ ]}
+ />
+ ,
+ ]}
+ />
+
+ }
+ tableProps={{
+ hasActions: true,
+ isSelectable: true,
+ selection: {
+ onSelectionChange: () => {},
+ },
+ }}
+ />
+ {isFlyoutVisible ? (
+ setIsFlyoutVisible(false)}
+ aria-labelledby='flyout-agent'
+ >
+
+
+
+
+ {agent.agent.name}
+
+
+
+
+
+
+
+
+
+
+ ) : null}
+ >
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/agents/list/visualizations.tsx b/plugins/wazuh-fleet/public/components/agents/list/visualizations.tsx
new file mode 100644
index 0000000000..357b882c3d
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/agents/list/visualizations.tsx
@@ -0,0 +1,191 @@
+import React from 'react';
+import { ViewMode } from '../../../../../../src/plugins/embeddable/public';
+import { getPlugins } from '../../../plugin-services';
+
+const getVisTopOS = (indexPatternId: string) => {
+ return getPieVis(indexPatternId, 'host.os.name', 'Top 5 OS', 'top_5_os');
+};
+
+const getVisTopGroups = (indexPatternId: string) => {
+ return getPieVis(
+ indexPatternId,
+ 'agent.groups',
+ 'Top 5 Groups',
+ 'top_5_groups',
+ );
+};
+
+const getVisTopAgentByNode = (indexPatternId: string) => {
+ return getPieVis(
+ indexPatternId,
+ 'agent.node_name',
+ 'Top 5 Agents by Node',
+ 'top_5_agents_by_node',
+ );
+};
+
+const getPieVis = (
+ indexPatternId: string,
+ field: string,
+ title: string,
+ id: string,
+) => {
+ return {
+ id,
+ title,
+ type: 'pie',
+ params: {
+ addLegend: true,
+ addTooltip: true,
+ isDonut: true,
+ labels: {
+ last_level: true,
+ show: false,
+ truncate: 100,
+ values: true,
+ },
+ legendPosition: 'right',
+ row: true,
+ type: 'pie',
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: '',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field,
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ },
+ schema: 'segment',
+ },
+ ],
+ },
+ };
+};
+
+const VIS_HEIGHT = 10;
+const VIS_WIDTH = 16;
+
+const PANEL_VIS_INDEX = 1;
+
+export const getKPIsPanel = (
+ indexPatternId: string,
+): {
+ [panelId: string]: DashboardPanelState<
+ EmbeddableInput & { [k: string]: unknown }
+ >;
+} => {
+ return {
+ '1': {
+ gridData: {
+ w: VIS_WIDTH,
+ h: VIS_HEIGHT,
+ x: 0,
+ y: 0,
+ i: '1',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '1',
+ savedVis: getVisTopOS(indexPatternId),
+ },
+ },
+ '2': {
+ gridData: {
+ w: VIS_WIDTH,
+ h: VIS_HEIGHT,
+ x: VIS_WIDTH,
+ y: 0,
+ i: '2',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '2',
+ savedVis: getVisTopGroups(indexPatternId),
+ },
+ },
+ '3': {
+ gridData: {
+ w: VIS_WIDTH,
+ h: VIS_HEIGHT,
+ x: VIS_WIDTH * 2,
+ y: 0,
+ i: '3',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '3',
+ savedVis: getVisTopAgentByNode(indexPatternId),
+ },
+ },
+ };
+};
+
+export const AgentsVisualizations = () => {
+ const plugins = getPlugins();
+
+ const DashboardByRenderer =
+ plugins.dashboard.DashboardContainerByValueRenderer;
+
+ return (
+ <>
+
+ >
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/commands/index.ts b/plugins/wazuh-fleet/public/components/commands/index.ts
new file mode 100644
index 0000000000..19b79d13ef
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/commands/index.ts
@@ -0,0 +1 @@
+export { CommandsList } from './list';
diff --git a/plugins/wazuh-fleet/public/components/commands/list/columns.tsx b/plugins/wazuh-fleet/public/components/commands/list/columns.tsx
new file mode 100644
index 0000000000..354476262a
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/commands/list/columns.tsx
@@ -0,0 +1,159 @@
+import React from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHealth,
+ EuiToolTip,
+ EuiText,
+ EuiBadge,
+ EuiLink,
+ EuiIcon,
+} from '@elastic/eui';
+import { getCore, getWazuhCore } from '../../../plugin-services';
+import { AgentGroups, HostOS } from '../../common';
+
+function capitalizeFirstLetter(value: string): string {
+ if (!value) return value;
+ return value.charAt(0).toUpperCase() + value.slice(1);
+}
+
+export const columnsTableActions = ({
+ setIsFlyoutAgentVisible,
+ setCommand,
+}: {
+ setIsFlyoutAgentVisible: (isVisible: boolean) => void;
+ setCommand: (command) => void;
+}) => [
+ {
+ name: 'View agent details',
+ description: 'View agent details',
+ icon: 'eye',
+ type: 'icon',
+ isPrimary: true,
+ color: 'primary',
+ onClick: item => {
+ setCommand(item);
+ setIsFlyoutAgentVisible(true);
+ },
+ },
+];
+
+export const tableColumns = ({
+ setIsFlyoutAgentVisible,
+ setCommand,
+}: {
+ setIsFlyoutAgentVisible: (isVisible: boolean) => void;
+ setCommand: (command) => void;
+}) => [
+ {
+ field: 'agent.id',
+ name: 'Agent ID',
+ sortable: true,
+ show: true,
+ searchable: true,
+ width: '140px',
+ render: (field: string, commandData) => (
+
+
+
+
+ {`${commandData?.agent?.id?.substring(0, 14) ?? '-'}...`}
+
+
+
+
+ ),
+ },
+ {
+ field: 'process.name',
+ name: 'Name',
+ sortable: true,
+ show: true,
+ searchable: true,
+ width: '100px',
+ },
+ {
+ field: 'process.command_line',
+ name: 'Command line',
+ sortable: true,
+ show: true,
+ searchable: true,
+ },
+ {
+ field: 'args',
+ name: 'Args',
+ sortable: true,
+ show: true,
+ searchable: true,
+ width: '230px',
+ render: (value: string[]) => {
+ return (
+
+ {value?.slice(0, 2).map(item => (
+
+ {item}
+
+ ))}
+
+ );
+ },
+ },
+ {
+ field: 'info',
+ name: 'Info',
+ sortable: true,
+ show: true,
+ searchable: true,
+ },
+ {
+ field: 'process.start',
+ name: 'Start',
+ sortable: true,
+ show: true,
+ searchable: true,
+ render: (value: Date) => {
+ const { utils } = getWazuhCore();
+ return utils.formatUIDate(value);
+ },
+ },
+ {
+ field: 'process.end',
+ name: 'End',
+ sortable: true,
+ show: true,
+ searchable: true,
+ render: (value: Date) => {
+ const { utils } = getWazuhCore();
+ return utils.formatUIDate(value);
+ },
+ },
+ {
+ field: 'status',
+ name: 'Status',
+ sortable: true,
+ show: true,
+ searchable: true,
+ render: (value: string) => {
+ let status = ['pending', 'sent', 'completed', 'failed'];
+ let colors = ['warning', 'primary', 'success', 'danger'];
+
+ return (
+
+
+
+
+
+ {capitalizeFirstLetter(value)}
+
+
+ );
+ },
+ },
+ {
+ field: 'actions',
+ name: 'Actions',
+ show: true,
+ width: '100px',
+ actions: columnsTableActions({ setIsFlyoutAgentVisible, setCommand }),
+ },
+];
diff --git a/plugins/wazuh-fleet/public/components/commands/list/index.tsx b/plugins/wazuh-fleet/public/components/commands/list/index.tsx
new file mode 100644
index 0000000000..19b79d13ef
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/commands/list/index.tsx
@@ -0,0 +1 @@
+export { CommandsList } from './list';
diff --git a/plugins/wazuh-fleet/public/components/commands/list/list.tsx b/plugins/wazuh-fleet/public/components/commands/list/list.tsx
new file mode 100644
index 0000000000..59b425fd65
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/commands/list/list.tsx
@@ -0,0 +1,59 @@
+import {
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiPageHeader,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import React, { useState } from 'react';
+import { tableColumns } from './columns';
+
+export interface CommandListProps {
+ FleetCommandsDataSource: any;
+ FleetCommandsDataSourceRepository: any;
+ TableIndexer: any;
+}
+
+import { getCore } from '../../../plugin-services';
+
+export const CommandsList = ({
+ FleetCommandsDataSource,
+ FleetCommandsDataSourceRepository,
+ TableIndexer,
+}: CommandListProps) => {
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const [command, setCommand] = useState();
+
+ return (
+ <>
+
+
+
+ {isFlyoutVisible ? (
+ setIsFlyoutVisible(false)}
+ aria-labelledby='flyout'
+ >
+
+
+ {command?.process?.name}
+
+
+
+
+ ) : null}
+ >
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/common/agent-groups.tsx b/plugins/wazuh-fleet/public/components/common/agent-groups.tsx
new file mode 100644
index 0000000000..2d1a026630
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/common/agent-groups.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui';
+
+export interface AgentGroupsProps {
+ groups: string[];
+}
+
+export const AgentGroups = ({ groups }: AgentGroupsProps) => {
+ return (
+
+ {groups?.map(group => (
+
+ {group}
+
+ ))}
+
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/common/host-os.tsx b/plugins/wazuh-fleet/public/components/common/host-os.tsx
new file mode 100644
index 0000000000..7790e91b00
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/common/host-os.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+
+export interface HostOSProps {
+ os: {
+ name: string;
+ platform: string;
+ full: string;
+ };
+}
+
+export const HostOS = ({ os }: HostOSProps) => {
+ let icon = '';
+ if (os?.platform === 'linux') {
+ icon = 'linux';
+ } else if (os?.platform === 'windows') {
+ icon = 'windows';
+ } else if (os?.platform === 'darwin') {
+ icon = 'apple';
+ }
+
+ return (
+
+
+
+ {' '}
+ {os?.full || '-'}
+
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/common/index.tsx b/plugins/wazuh-fleet/public/components/common/index.tsx
new file mode 100644
index 0000000000..37d1212810
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/common/index.tsx
@@ -0,0 +1,2 @@
+export { AgentGroups } from './agent-groups';
+export { HostOS } from './host-os';
diff --git a/plugins/wazuh-fleet/public/components/fleet-management.tsx b/plugins/wazuh-fleet/public/components/fleet-management.tsx
new file mode 100644
index 0000000000..859175beb1
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/fleet-management.tsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import {
+ EuiPage,
+ EuiPageBody,
+ EuiPageSideBar,
+ EuiSideNav,
+ EuiPanel,
+} from '@elastic/eui';
+import { AgentList } from './agents';
+import { GroupList } from './groups/list/list';
+import { Route, Switch, Redirect } from 'react-router-dom';
+import { getCore } from '../plugin-services';
+import { AgentDetails } from './agents/details';
+import { CommandsList } from './commands';
+
+const views = [
+ {
+ name: 'Agents',
+ id: 'agents',
+ hasDetailsRoute: true,
+ render: (props: any) => ,
+ },
+ {
+ name: 'Groups',
+ id: 'groups',
+ hasDetailsRoute: true,
+ render: (props: any) => ,
+ },
+ {
+ name: 'Agents commands',
+ id: 'commands',
+ render: (props: any) => ,
+ },
+ {
+ name: 'Comms configurations',
+ id: 'comms-configurations',
+ render: () => Comms configurations
,
+ },
+];
+
+export interface FleetManagementProps {
+ navigationService: any;
+ FleetDataSource: any;
+ FleetDataSourceRepository: any;
+ TableIndexer: any;
+ useTimeFilter: any;
+ LoadingSpinner: any;
+ AlertsDataSource: any;
+ AlertsDataSourceRepository: any;
+}
+
+export const FleetManagement = ({
+ navigationService,
+ ...restProps
+}: FleetManagementProps) => {
+ const sideNav = [
+ {
+ name: 'Fleet Management',
+ id: 'fleet-management',
+ items: views.map(item => ({
+ ...item,
+ onClick: () => {
+ navigationService
+ .getInstance()
+ .navigate(`/fleet-management/${item.id}`);
+ },
+ isSelected: navigationService
+ .getInstance()
+ .getLocation()
+ .pathname.startsWith(`/fleet-management/${item.id}`),
+ })),
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+ {views.reduce((acc, item) => {
+ return [
+ ...acc,
+ item.hasDetailsRoute ? (
+ {
+ getCore().chrome.setBreadcrumbs([
+ { text: 'Fleet Management' },
+ {
+ text: item.name,
+ href: getCore().application.getUrlForApp(
+ 'fleet-management',
+ {
+ path: `#/fleet-management/${item.id}`,
+ },
+ ),
+ },
+ { text: `ID / ${props.match.params.id}` },
+ ]);
+
+ if (item.id === 'agents') {
+ return ;
+ }
+
+ if (item.id === 'groups') {
+ return Group
;
+ }
+ }}
+ />
+ ) : null,
+ {
+ getCore().chrome.setBreadcrumbs([
+ { text: 'Fleet Management' },
+ { text: item.name },
+ ]);
+ return item.render(restProps);
+ }}
+ />,
+ ];
+ }, [])}
+
+
+
+
+
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/groups/index.tsx b/plugins/wazuh-fleet/public/components/groups/index.tsx
new file mode 100644
index 0000000000..2a74476219
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/groups/index.tsx
@@ -0,0 +1 @@
+export { GroupList } from './list/list';
diff --git a/plugins/wazuh-fleet/public/components/groups/list/actions/actions.tsx b/plugins/wazuh-fleet/public/components/groups/list/actions/actions.tsx
new file mode 100644
index 0000000000..44180e5ef7
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/groups/list/actions/actions.tsx
@@ -0,0 +1,37 @@
+export const tableActions = ({
+ setIsFlyoutVisible,
+ setGroup,
+}: {
+ setIsFlyoutVisible: (isVisible: boolean) => void;
+ setGroup: (Group: Group) => void;
+}) => [
+ {
+ name: 'View Group details',
+ description: 'View Group details',
+ icon: 'eye',
+ type: 'icon',
+ isPrimary: true,
+ color: 'primary',
+ onClick: (Group: Group) => {
+ setGroup(Group);
+ setIsFlyoutVisible(true);
+ },
+ },
+ {
+ name: 'Edit group',
+ description: 'Edit group',
+ icon: 'pencil',
+ type: 'icon',
+ onClick: (Group: Group) => {
+ setGroup(Group);
+ setIsFlyoutVisible(true);
+ },
+ },
+ {
+ name: 'Delete',
+ description: 'Delete',
+ icon: 'trash',
+ type: 'icon',
+ onClick: () => {},
+ },
+];
diff --git a/plugins/wazuh-fleet/public/components/groups/list/columns.tsx b/plugins/wazuh-fleet/public/components/groups/list/columns.tsx
new file mode 100644
index 0000000000..6edabd511a
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/groups/list/columns.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { tableActions } from './actions/actions';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHealth,
+ EuiToolTip,
+ EuiText,
+ EuiBadge,
+ EuiLink,
+} from '@elastic/eui';
+import { getCore, getWazuhCore } from '../../../plugin-services';
+
+export const columns = ({
+ setIsFlyoutVisible,
+ setGroup,
+}: {
+ setIsFlyoutVisible: (isVisible: boolean) => void;
+ setGroup: (agent: Group) => void;
+}) => [
+ {
+ field: 'id',
+ name: 'ID',
+ sortable: true,
+ show: true,
+ searchable: true,
+ render: (field: string, data: Group) => (
+
+
+
+ {`${data.id.substring(0, 22)}...`}
+
+
+
+ ),
+ },
+ {
+ field: 'name',
+ name: 'Name',
+ sortable: true,
+ show: true,
+ searchable: true,
+ render: (field: string, data: Agent) => (
+
+
+
+ {data.name}
+
+
+
+ ),
+ },
+ {
+ field: 'agents',
+ name: 'Agents',
+ sortable: true,
+ show: true,
+ searchable: true,
+ },
+ {
+ field: 'actions',
+ name: 'Actions',
+ show: true,
+ actions: tableActions({ setIsFlyoutVisible, setGroup }),
+ },
+];
diff --git a/plugins/wazuh-fleet/public/components/groups/list/list.tsx b/plugins/wazuh-fleet/public/components/groups/list/list.tsx
new file mode 100644
index 0000000000..b340a2e525
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/groups/list/list.tsx
@@ -0,0 +1,85 @@
+import React, { useState } from 'react';
+import {
+ EuiPageHeader,
+ EuiSpacer,
+ EuiButton,
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiLink,
+ EuiFlyoutBody,
+ EuiText,
+} from '@elastic/eui';
+import { columns } from './columns';
+import { getCore } from '../../../plugin-services';
+
+export interface AgentListProps {
+ FleetGroupsDataSource: any;
+ FleetGroupsDataSourceRepository: any;
+ TableIndexer: any;
+}
+
+export const GroupList = ({
+ FleetGroupsDataSource,
+ FleetGroupsDataSourceRepository,
+ TableIndexer,
+}: AgentListProps) => {
+ const [isActionsOpen, setIsActionsOpen] = useState(false);
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const [group, setGroup] = useState();
+
+ const handleOnOpenDetails = (agentId: string) => {};
+
+ return (
+ <>
+
+ Add new group
+ ,
+ ]}
+ // rightSideGroupProps={{ gutterSize: 's' }}
+ />
+
+
+ {isFlyoutVisible ? (
+ setIsFlyoutVisible(false)}
+ aria-labelledby='flyout'
+ >
+
+
+
+
+ {group.name}
+
+
+
+
+
+
+
+
+ ) : null}
+ >
+ );
+};
diff --git a/plugins/wazuh-fleet/public/components/index.tsx b/plugins/wazuh-fleet/public/components/index.tsx
new file mode 100644
index 0000000000..ea32ce6d55
--- /dev/null
+++ b/plugins/wazuh-fleet/public/components/index.tsx
@@ -0,0 +1 @@
+export { FleetManagement } from './fleet-management';
diff --git a/plugins/wazuh-fleet/public/index.ts b/plugins/wazuh-fleet/public/index.ts
new file mode 100644
index 0000000000..7277e6c6eb
--- /dev/null
+++ b/plugins/wazuh-fleet/public/index.ts
@@ -0,0 +1,8 @@
+import { WazuhFleetPlugin } from './plugin';
+
+// This exports static code and TypeScript types,
+// as well as, OpenSearch Dashboards Platform `plugin()` initializer.
+export function plugin() {
+ return new WazuhFleetPlugin();
+}
+export { WazuhFleetPluginSetup, WazuhFleetPluginStart } from './types';
diff --git a/plugins/wazuh-fleet/public/plugin-services.ts b/plugins/wazuh-fleet/public/plugin-services.ts
new file mode 100644
index 0000000000..978706134a
--- /dev/null
+++ b/plugins/wazuh-fleet/public/plugin-services.ts
@@ -0,0 +1,10 @@
+import { CoreStart } from 'opensearch-dashboards/public';
+import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/common';
+import { WazuhCorePluginStart } from '../../wazuh-core/public';
+import { AppPluginStartDependencies } from './types';
+
+export const [getPlugins, setPlugins] =
+ createGetterSetter('Plugins');
+export const [getCore, setCore] = createGetterSetter('Core');
+export const [getWazuhCore, setWazuhCore] =
+ createGetterSetter('WazuhCore');
diff --git a/plugins/wazuh-fleet/public/plugin.ts b/plugins/wazuh-fleet/public/plugin.ts
new file mode 100644
index 0000000000..3253a55bff
--- /dev/null
+++ b/plugins/wazuh-fleet/public/plugin.ts
@@ -0,0 +1,31 @@
+import { CoreSetup, CoreStart, Plugin } from 'opensearch-dashboards/public';
+import {
+ AppPluginStartDependencies,
+ WazuhFleetPluginSetup,
+ WazuhFleetPluginStart,
+} from './types';
+import { FleetManagement } from './components';
+import { setCore, setPlugins, setWazuhCore } from './plugin-services';
+
+export class WazuhFleetPlugin
+ implements Plugin
+{
+ public setup(core: CoreSetup): WazuhFleetPluginSetup {
+ return {};
+ }
+
+ public start(
+ core: CoreStart,
+ plugins: AppPluginStartDependencies,
+ ): WazuhFleetPluginStart {
+ setCore(core);
+ setPlugins(plugins);
+ setWazuhCore(plugins.wazuhCore);
+
+ return {
+ FleetManagement,
+ };
+ }
+
+ public stop() {}
+}
diff --git a/plugins/wazuh-fleet/public/types.ts b/plugins/wazuh-fleet/public/types.ts
new file mode 100644
index 0000000000..e398494181
--- /dev/null
+++ b/plugins/wazuh-fleet/public/types.ts
@@ -0,0 +1,14 @@
+import { WazuhCorePluginStart } from '../../wazuh-core/public';
+import { FleetManagementProps } from './components/fleet-management';
+import { DashboardStart } from '../../../src/plugins/dashboard/public';
+
+export interface WazuhFleetPluginSetup {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface WazuhFleetPluginStart {
+ FleetManagement: (props: FleetManagementProps) => JSX.Element;
+}
+
+export interface AppPluginStartDependencies {
+ wazuhCore: WazuhCorePluginStart;
+ dashboard: DashboardStart;
+}
diff --git a/plugins/wazuh-fleet/scripts/jest.js b/plugins/wazuh-fleet/scripts/jest.js
new file mode 100644
index 0000000000..9d6bc3ae8d
--- /dev/null
+++ b/plugins/wazuh-fleet/scripts/jest.js
@@ -0,0 +1,22 @@
+// # Run Jest tests
+//
+// All args will be forwarded directly to Jest, e.g. to watch tests run:
+//
+// node scripts/jest --watch
+//
+// or to build code coverage:
+//
+// node scripts/jest --coverage
+//
+// See all cli options in https://facebook.github.io/jest/docs/cli.html
+
+const path = require('path');
+process.argv.push(
+ '--config',
+ path.resolve(__dirname, '../test/jest/config.js'),
+);
+
+require('../../../src/setup_node_env');
+const jest = require('../../../node_modules/jest');
+
+jest.run(process.argv.slice(2));
diff --git a/plugins/wazuh-fleet/scripts/manifest.js b/plugins/wazuh-fleet/scripts/manifest.js
new file mode 100644
index 0000000000..d98460c0fc
--- /dev/null
+++ b/plugins/wazuh-fleet/scripts/manifest.js
@@ -0,0 +1,16 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+
+const fs = require('fs');
+
+/**
+ * Reads the package.json file.
+ * @returns {Object} JSON object.
+ */
+function loadPackageJson() {
+ const packageJson = fs.readFileSync('./package.json');
+ return JSON.parse(packageJson);
+}
+
+module.exports = {
+ loadPackageJson,
+};
diff --git a/plugins/wazuh-fleet/scripts/runner.js b/plugins/wazuh-fleet/scripts/runner.js
new file mode 100755
index 0000000000..5ba9b132ab
--- /dev/null
+++ b/plugins/wazuh-fleet/scripts/runner.js
@@ -0,0 +1,148 @@
+/* eslint-disable array-element-newline */
+/* eslint-disable @typescript-eslint/no-var-requires */
+
+/**
+Runs yarn commands using a Docker container.
+
+Intended to test and build locally.
+
+Uses development images. Must be executed from the root folder of the project.
+
+See /docker/runner/docker-compose.yml for available environment variables.
+
+# Usage:
+# -------------
+# - node scripts/runner []
+# - yarn test:jest:runner []
+# - yarn build:runner
+*/
+
+const childProcess = require('child_process');
+const { loadPackageJson } = require('./manifest');
+
+const COMPOSE_DIR = '../../docker/runner';
+
+function getProjectInfo() {
+ const manifest = loadPackageJson();
+
+ return {
+ app: 'osd',
+ version: manifest['pluginPlatform']['version'],
+ repo: process.cwd(),
+ };
+}
+
+function getBuildArgs({ app, version }) {
+ return `--opensearch-dashboards-version=${version}`;
+}
+
+/**
+ * Transforms the Jest CLI options from process.argv back to a string.
+ * If no options are provided, default ones are generated.
+ * @returns {String} Space separated string with all Jest CLI options provided.
+ */
+function getJestArgs() {
+ // Take args only after `test` word
+ const index = process.argv.indexOf('test');
+ const args = process.argv.slice(index + 1);
+ // Remove duplicates using set
+ return Array.from(new Set([...args, '--runInBand'])).join(' ');
+}
+
+/**
+ * Generates the execution parameters if they are not set.
+ * @returns {Object} Default environment variables.
+ */
+const buildEnvVars = ({ app, version, repo, cmd, args }) => {
+ return {
+ APP: app,
+ VERSION: version,
+ REPO: repo,
+ CMD: cmd,
+ ARGS: args,
+ };
+};
+
+/**
+ * Captures the SIGINT signal (Ctrl + C) to stop the container and exit.
+ */
+function setupAbortController() {
+ process.on('SIGINT', () => {
+ childProcess.spawnSync('docker', [
+ 'compose',
+ '--project-directory',
+ COMPOSE_DIR,
+ 'stop',
+ ]);
+ process.exit();
+ });
+}
+
+/**
+ * Start the container.
+ */
+function startRunner() {
+ const runner = childProcess.spawn('docker', [
+ 'compose',
+ '--project-directory',
+ COMPOSE_DIR,
+ 'up',
+ ]);
+
+ runner.stdout.on('data', data => {
+ console.log(`${data}`);
+ });
+
+ runner.stderr.on('data', data => {
+ console.error(`${data}`);
+ });
+}
+
+/**
+ * Main function
+ */
+function main() {
+ if (process.argv.length < 2) {
+ process.stderr.write('Required parameters not provided');
+ process.exit(-1);
+ }
+
+ const projectInfo = getProjectInfo();
+ let envVars = {};
+
+ switch (process.argv[2]) {
+ case 'build':
+ envVars = buildEnvVars({
+ ...projectInfo,
+ cmd: 'plugin-helpers build',
+ args: getBuildArgs({ ...projectInfo }),
+ });
+ break;
+
+ case 'test':
+ envVars = buildEnvVars({
+ ...projectInfo,
+ cmd: 'test:jest',
+ args: getJestArgs(),
+ });
+ break;
+
+ default:
+ // usage();
+ console.error('Unsupported or invalid yarn command.');
+ process.exit(-1);
+ }
+
+ // Check the required environment variables are set
+ for (const [key, value] of Object.entries(envVars)) {
+ if (!process.env[key]) {
+ process.env[key] = value;
+ }
+ console.log(`${key}: ${process.env[key]}`);
+ }
+
+ setupAbortController();
+ startRunner();
+}
+
+main();
diff --git a/plugins/wazuh-fleet/server/index.ts b/plugins/wazuh-fleet/server/index.ts
new file mode 100644
index 0000000000..5f2c2c5412
--- /dev/null
+++ b/plugins/wazuh-fleet/server/index.ts
@@ -0,0 +1,11 @@
+import { PluginInitializerContext } from '../../../src/core/server';
+import { WazuhFleetPlugin } from './plugin';
+
+// This exports static code and TypeScript types,
+// as well as, OpenSearch Dashboards Platform `plugin()` initializer.
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new WazuhFleetPlugin(initializerContext);
+}
+
+export { WazuhFleetPluginSetup, WazuhFleetPluginStart } from './types';
diff --git a/plugins/wazuh-fleet/server/plugin-services.ts b/plugins/wazuh-fleet/server/plugin-services.ts
new file mode 100644
index 0000000000..8e354c54f8
--- /dev/null
+++ b/plugins/wazuh-fleet/server/plugin-services.ts
@@ -0,0 +1,14 @@
+import {
+ CoreStart,
+ ISavedObjectsRepository,
+} from 'opensearch-dashboards/server';
+import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/common';
+import { WazuhCorePluginStart } from '../../wazuh-core/server';
+
+export const [getInternalSavedObjectsClient, setInternalSavedObjectsClient] =
+ createGetterSetter('SavedObjectsRepository');
+export const [getCore, setCore] = createGetterSetter('Core');
+export const [getWazuhCore, setWazuhCore] =
+ createGetterSetter('WazuhCore');
+export const [getWazuhFleetServices, setWazuhFleetServices] =
+ createGetterSetter('WazuhFleetServices');
diff --git a/plugins/wazuh-fleet/server/plugin.ts b/plugins/wazuh-fleet/server/plugin.ts
new file mode 100644
index 0000000000..96641ea35d
--- /dev/null
+++ b/plugins/wazuh-fleet/server/plugin.ts
@@ -0,0 +1,74 @@
+import {
+ PluginInitializerContext,
+ CoreSetup,
+ CoreStart,
+ Plugin,
+ Logger,
+} from 'opensearch-dashboards/server';
+
+import {
+ PluginSetup,
+ WazuhFleetPluginSetup,
+ WazuhFleetPluginStart,
+ AppPluginStartDependencies,
+} from './types';
+
+import {
+ setCore,
+ setWazuhCore,
+ setInternalSavedObjectsClient,
+ setWazuhFleetServices,
+} from './plugin-services';
+import { ISecurityFactory } from '../../wazuh-core/server/services/security-factory';
+
+declare module 'opensearch-dashboards/server' {
+ interface RequestHandlerContext {
+ wazuh_fleet: {
+ logger: Logger;
+ security: ISecurityFactory;
+ };
+ }
+}
+
+export class WazuhFleetPlugin
+ implements Plugin
+{
+ private readonly logger: Logger;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.logger = initializerContext.logger.get();
+ }
+
+ public async setup(core: CoreSetup, plugins: PluginSetup) {
+ this.logger.debug('wazuh_fleet: Setup');
+
+ setWazuhCore(plugins.wazuhCore);
+ setWazuhFleetServices({ logger: this.logger });
+
+ core.http.registerRouteHandlerContext('wazuh_fleet', () => {
+ return {
+ logger: this.logger,
+ security: plugins.wazuhCore.dashboardSecurity,
+ };
+ });
+
+ return {};
+ }
+
+ public start(
+ core: CoreStart,
+ plugins: AppPluginStartDependencies,
+ ): WazuhFleetPluginStart {
+ this.logger.debug('wazuhFleet: Started');
+
+ const internalSavedObjectsClient =
+ core.savedObjects.createInternalRepository();
+ setCore(core);
+
+ setInternalSavedObjectsClient(internalSavedObjectsClient);
+
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/plugins/wazuh-fleet/server/types.ts b/plugins/wazuh-fleet/server/types.ts
new file mode 100644
index 0000000000..47a9f6b4a0
--- /dev/null
+++ b/plugins/wazuh-fleet/server/types.ts
@@ -0,0 +1,18 @@
+import { ISecurityFactory } from '../../wazuh-core/server/services/security-factory';
+import { WazuhCorePluginStart } from '../../wazuh-core/server';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface AppPluginStartDependencies {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface WazuhFleetPluginSetup {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface WazuhFleetPluginStart {}
+
+export type PluginSetup = {
+ securityDashboards?: {}; // TODO: Add OpenSearch Dashboards Security interface
+ wazuhCore: { dashboardSecurity: ISecurityFactory };
+};
+
+export interface AppPluginStartDependencies {
+ wazuhCore: WazuhCorePluginStart;
+}
diff --git a/plugins/wazuh-fleet/test/jest/config.js b/plugins/wazuh-fleet/test/jest/config.js
new file mode 100644
index 0000000000..c49cd92aa0
--- /dev/null
+++ b/plugins/wazuh-fleet/test/jest/config.js
@@ -0,0 +1,41 @@
+import path from 'path';
+
+const kbnDir = path.resolve(__dirname, '../../../../');
+
+export default {
+ rootDir: path.resolve(__dirname, '../..'),
+ roots: ['/public', '/server', '/common'],
+ modulePaths: [`${kbnDir}/node_modules`],
+ collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', './!**/node_modules/**'],
+ moduleNameMapper: {
+ '^ui/(.*)': `${kbnDir}/src/ui/public/$1`,
+ // eslint-disable-next-line max-len
+ '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `${kbnDir}/src/dev/jest/mocks/file_mock.js`,
+ '\\.(css|less|scss)$': `${kbnDir}/src/dev/jest/mocks/style_mock.js`,
+ axios: 'axios/dist/node/axios.cjs',
+ },
+ setupFiles: [
+ `${kbnDir}/src/dev/jest/setup/babel_polyfill.js`,
+ `${kbnDir}/src/dev/jest/setup/enzyme.js`,
+ ],
+ collectCoverage: true,
+ coverageDirectory: './target/test-coverage',
+ coverageReporters: ['html', 'text-summary', 'json-summary'],
+ globals: {
+ 'ts-jest': {
+ skipBabel: true,
+ },
+ },
+ moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'html'],
+ modulePathIgnorePatterns: ['__fixtures__/', 'target/'],
+ testMatch: ['**/*.test.{js,ts,tsx}'],
+ transform: {
+ '^.+\\.js$': `${kbnDir}/src/dev/jest/babel_transform.js`,
+ '^.+\\.tsx?$': `${kbnDir}/src/dev/jest/babel_transform.js`,
+ '^.+\\.html?$': `${kbnDir}/src/dev/jest/babel_transform.js`,
+ },
+ transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.js$'],
+ snapshotSerializers: [`${kbnDir}/node_modules/enzyme-to-json/serializer`],
+ testEnvironment: 'jest-environment-jsdom',
+ reporters: ['default', `${kbnDir}/src/dev/jest/junit_reporter.js`],
+};
diff --git a/plugins/wazuh-fleet/translations/en-US.json b/plugins/wazuh-fleet/translations/en-US.json
new file mode 100644
index 0000000000..9022cc65e3
--- /dev/null
+++ b/plugins/wazuh-fleet/translations/en-US.json
@@ -0,0 +1,79 @@
+{
+ "formats": {
+ "number": {
+ "currency": {
+ "style": "currency"
+ },
+ "percent": {
+ "style": "percent"
+ }
+ },
+ "date": {
+ "short": {
+ "month": "numeric",
+ "day": "numeric",
+ "year": "2-digit"
+ },
+ "medium": {
+ "month": "short",
+ "day": "numeric",
+ "year": "numeric"
+ },
+ "long": {
+ "month": "long",
+ "day": "numeric",
+ "year": "numeric"
+ },
+ "full": {
+ "weekday": "long",
+ "month": "long",
+ "day": "numeric",
+ "year": "numeric"
+ }
+ },
+ "time": {
+ "short": {
+ "hour": "numeric",
+ "minute": "numeric"
+ },
+ "medium": {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric"
+ },
+ "long": {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short"
+ },
+ "full": {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short"
+ }
+ },
+ "relative": {
+ "years": {
+ "units": "year"
+ },
+ "months": {
+ "units": "month"
+ },
+ "days": {
+ "units": "day"
+ },
+ "hours": {
+ "units": "hour"
+ },
+ "minutes": {
+ "units": "minute"
+ },
+ "seconds": {
+ "units": "second"
+ }
+ }
+ },
+ "messages": {}
+}
diff --git a/plugins/wazuh-fleet/tsconfig.json b/plugins/wazuh-fleet/tsconfig.json
new file mode 100644
index 0000000000..cc7e3e157f
--- /dev/null
+++ b/plugins/wazuh-fleet/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "index.ts",
+ "common/**/*.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "server/**/*.ts",
+ "../../typings/**/*",
+ "public/hooks"
+ ],
+ "exclude": []
+}
diff --git a/plugins/wazuh-fleet/yarn.lock b/plugins/wazuh-fleet/yarn.lock
new file mode 100644
index 0000000000..facd9bb020
--- /dev/null
+++ b/plugins/wazuh-fleet/yarn.lock
@@ -0,0 +1,98 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@testing-library/user-event@^14.5.0":
+ version "14.5.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.0.tgz#4036add379525b635a64bce4d727820d4ba516a7"
+ integrity sha512-nQRCteEZvULJJrlcGQuNhwGekz25TOUILA+sTWI9PB/vNKKivS+7K7XRTwoikw/2fmJPaM4pPKy+hLWEGg9+JA==
+
+"@types/@testing-library/user-event":
+ version "0.0.0-semantically-released"
+ resolved "https://codeload.github.com/testing-library/user-event/tar.gz/4be87b3452f524bcc256d43cfb891ba1f0e236d6"
+
+"@types/md5@^2.3.2":
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.2.tgz#529bb3f8a7e9e9f621094eb76a443f585d882528"
+ integrity sha512-v+JFDu96+UYJ3/UWzB0mEglIS//MZXgRaJ4ubUPwOM0gvLc/kcQ3TWNYwENEK7/EcXGQVrW8h/XqednSjBd/Og==
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+axios@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2"
+ integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
+charenc@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
+ integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
+
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+crypt@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
+ integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+follow-redirects@^1.15.6:
+ version "1.15.6"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
+ integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
+
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
+is-buffer@~1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+ integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+
+md5@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
+ integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
+ dependencies:
+ charenc "0.0.2"
+ crypt "0.0.2"
+ is-buffer "~1.1.6"
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
diff --git a/scripts/wazuh-fleet-commands-generator/DIS_Settings.json b/scripts/wazuh-fleet-commands-generator/DIS_Settings.json
new file mode 100644
index 0000000000..c00e13e339
--- /dev/null
+++ b/scripts/wazuh-fleet-commands-generator/DIS_Settings.json
@@ -0,0 +1,7 @@
+{
+ "ip": "127.0.0.1",
+ "port": "9200",
+ "username": "admin",
+ "password": "admin",
+ "index": "commands-default"
+}
diff --git a/scripts/wazuh-fleet-commands-generator/DIS_Template.json b/scripts/wazuh-fleet-commands-generator/DIS_Template.json
new file mode 100644
index 0000000000..046ae784ca
--- /dev/null
+++ b/scripts/wazuh-fleet-commands-generator/DIS_Template.json
@@ -0,0 +1,99 @@
+{
+ "mappings": {
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "message": {
+ "type": "text"
+ },
+ "process": {
+ "properties": {
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "type": "text"
+ },
+ "end": {
+ "type": "date"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "tags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "wazuh": {
+ "properties": {
+ "cluster": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "manager": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "codec": "best_compression",
+ "mapping": {
+ "total_fields": {
+ "limit": 1000
+ }
+ },
+ "refresh_interval": "2s"
+ }
+ }
+}
diff --git a/scripts/wazuh-fleet-commands-generator/DIS_Template_Legacy.json b/scripts/wazuh-fleet-commands-generator/DIS_Template_Legacy.json
new file mode 100644
index 0000000000..cbdef8607f
--- /dev/null
+++ b/scripts/wazuh-fleet-commands-generator/DIS_Template_Legacy.json
@@ -0,0 +1,104 @@
+{
+ "index_patterns": ["commands-default"],
+ "priority": 1,
+ "template": {
+ "settings": {
+ "index": {
+ "codec": "zstd",
+ "number_of_replicas": "0",
+ "number_of_shards": "1",
+ "query.default_field": ["args", "agent.id", "status", "info"],
+ "refresh_interval": "5s"
+ }
+ },
+ "mappings": {
+ "date_detection": false,
+ "dynamic": "strict",
+ "properties": {
+ "message": {
+ "type": "text"
+ },
+ "process": {
+ "properties": {
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "type": "text"
+ },
+ "end": {
+ "type": "date"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "tags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "wazuh": {
+ "properties": {
+ "cluster": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "manager": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "agent": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "info": {
+ "type": "text"
+ },
+ "args": {
+ "type": "text"
+ }
+ }
+ }
+ }
+}
diff --git a/scripts/wazuh-fleet-commands-generator/dataInjectScript.py b/scripts/wazuh-fleet-commands-generator/dataInjectScript.py
new file mode 100644
index 0000000000..5230d47b6e
--- /dev/null
+++ b/scripts/wazuh-fleet-commands-generator/dataInjectScript.py
@@ -0,0 +1,290 @@
+from datetime import timedelta, datetime
+from opensearchpy import OpenSearch, helpers
+import random
+import json
+import os.path
+import warnings
+import uuid
+
+warnings.filterwarnings("ignore")
+def generateRandomDate(days_interval=10):
+ start_date = datetime.now()
+ end_date = start_date - timedelta(days=days_interval)
+ random_date = start_date + (end_date - start_date) * random.random()
+ return(random_date.strftime("%Y-%m-%dT%H:%M:%S.{}Z".format(random.randint(0, 999))))
+
+def generateRandomGroups():
+ groups = ['default', 'group1', 'group2', 'group3', 'group4', 'group5']
+ return groups
+
+def generateNodeNames():
+ nodes = ['wazuh', 'node1', 'node2', 'node3', 'node4', 'node5']
+ return nodes
+
+def generateRandomUUID():
+ return str(uuid.uuid4())
+
+def generateRandomAgent():
+ agent={}
+ agent['build'] = {'original':'build{}'.format(random.randint(0, 9999))}
+ agent['id'] = '00{}'.format(random.randint(1, 99))
+ agent['name'] = 'Agent{}'.format(random.randint(0, 99))
+ agent['version'] = 'v{}-stable'.format(random.randint(0, 9))
+ agent['ephemeral_id'] = '{}'.format(random.randint(0, 99999))
+ agent['groups'] = random.sample(generateRandomGroups(), random.randint(1, len(generateRandomGroups())))
+ agent['node_name'] = random.choice(generateNodeNames())
+ agent['created'] = generateRandomDate()
+ return(agent)
+
+def generateRandomHost():
+ host = {}
+ family=random.choice(['debian','ubuntu','macos','ios','android','RHEL'])
+ version='{}.{}'.format(random.randint(0, 99),random.randint(0, 99))
+ host['os'] = {
+ 'family': family,
+ 'full': family + ' ' + version,
+ 'kernel': version+'kernel{}'.format(random.randint(0, 99)),
+ 'name': family + ' ' + version,
+ 'platform': family,
+ 'type': random.choice(['windows','linux','macos','ios','android','unix']),
+ 'version': version
+ }
+ return(host)
+
+def generateRandomWazuh():
+ wazuh = {}
+ wazuh['cluster'] = {'name':random.choice(['wazuh.manager', 'wazuh']), 'node':random.choice(['master','worker-01','worker-02','worker-03'])}
+ return(wazuh)
+
+# Add command line example data. Example: ["/usr/bin/ssh", "-l", "user", "10.0.0.16"]
+def generateRandomCommandLine():
+ command_line = [
+ '/usr/bin/ssh',
+ '-l',
+ 'user',
+ '10.0.0.16',
+ '/usr/bin/curl',
+ '-X',
+ 'GET',
+ 'https://example.com',
+ '/usr/bin/ping',
+ 'localhost',
+ '/usr/bin/ls',
+ '-l',
+ '/usr/bin/grep',
+ 'pattern',
+ '/usr/bin/cat',
+ '/path/to/file',
+ '/usr/bin/mkdir',
+ '/path/to/directory',
+ '/usr/bin/rm',
+ '/path/to/file',
+ '/usr/bin/mv',
+ '/path/to/source',
+ '/path/to/destination',
+ '/usr/bin/chmod',
+ '755',
+ '/path/to/file',
+ '/usr/bin/chown',
+ 'user:group',
+ '/path/to/file',
+ '/usr/bin/git',
+ 'clone',
+ 'https://github.com/example/repo.git'
+ ]
+ return command_line
+"""
+Process name.
+Sometimes called program name or similar.
+example: ssh
+"""
+def getProcessName():
+ process_name = [
+ 'ssh',
+ 'curl',
+ 'ping',
+ 'ls',
+ 'grep',
+ 'cat',
+ 'mkdir',
+ 'rm',
+ 'mv',
+ 'chmod',
+ 'chown',
+ 'git'
+ ]
+ return process_name
+
+# Array of process arguments, starting with the absolute path to the executable.
+def getProcessArgs():
+ args = [
+ '/usr/bin/ssh',
+ '/usr/bin/curl',
+ '/usr/bin/ping',
+ '/usr/bin/ls',
+ '/usr/bin/grep',
+ '/usr/bin/cat',
+ '/usr/bin/mkdir',
+ '/usr/bin/rm',
+ '/usr/bin/mv',
+ '/usr/bin/chmod',
+ '/usr/bin/chown',
+ '/usr/bin/git'
+ ]
+ return args
+
+# One of: ["pending", "sent", "completed", "failed"]**
+def getCommandStatus():
+ status = ['pending', 'sent', 'completed', 'failed']
+ return status
+
+# additional information of the execution of the command. For example, the error message.
+def getCommandInfo():
+ info = [
+ 'Error: Connection refused',
+ 'Error: File not found',
+ 'Error: Invalid argument',
+ 'Error: Permission denied'
+ ]
+ return info
+
+def generateRandomProcess():
+ process = {}
+ process['command_line'] = random.choice(generateRandomCommandLine())
+ process['end'] = generateRandomDate()
+ process['start'] = generateRandomDate()
+ process['name'] = random.choice(getProcessName())
+ return process
+
+def generateRandomData(number):
+ for i in range(0, int(number)):
+ yield {
+ 'process': generateRandomProcess(),
+ 'args': random.sample(getProcessArgs(), random.randint(1, len(getProcessArgs()))),
+ 'agent': {
+ 'id': generateRandomUUID(),
+ },
+ 'status': random.choice(getCommandStatus()),
+ 'info': random.choice(getCommandInfo()),
+ 'wazuh': generateRandomWazuh()
+ }
+
+def verifyIndex(index,instance):
+ if not instance.indices.exists(index):
+ if os.path.exists('DIS_Template.json'):
+ print('\nIndex {} does not exist. Trying to create it with the template in DIS_Template.json'.format(index))
+ with open('DIS_Template.json') as templateFile:
+ template = json.load(templateFile)
+ try:
+ instance.indices.create(index=index, body=template)
+ indexExist = True
+ print('Done!')
+ except Exception as e:
+ print('Error: {}'.format(e))
+ else:
+ notemplate=input('\nIndex {} does not exist. Template file not found. Continue without template? (y/n)'.format(index))
+ while notemplate != 'y' and notemplate != 'n':
+ notemplate=input('\nInvalid option. Continue without template? (y/n) \n')
+ if notemplate == 'n':
+ return False
+ print('\nTrying to create index {} without template'.format(index))
+ try:
+ instance.indices.create(index=index)
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return True
+
+def verifySettings():
+ verified = False
+ if os.path.exists('DIS_Settings.json'):
+ with open('DIS_Settings.json') as configFile:
+ config = json.load(configFile)
+ if 'ip' not in config or 'port' not in config or 'index' not in config or 'username' not in config or 'password' not in config:
+ print('\nDIS_Settings.json is not properly configured. Continuing without it.')
+ else:
+ verified = True
+ else:
+ print('\nDIS_Settings.json not found. Continuing without it.')
+
+ if not verified:
+ ip = input("\nEnter the IP of your Indexer [default=0.0.0.0]: \n")
+ if ip == '':
+ ip = '0.0.0.0'
+
+ port = input("\nEnter the port of your Indexer [default=9200]: \n")
+ if port == '':
+ port = '9200'
+
+ index = input("\nEnter the index name [default=wazuh-fleet-groups-default]: \n")
+ if index == '':
+ index = 'wazuh-fleet-groups-default'
+
+ url = 'https://{}:{}/{}/_doc'.format(ip, port, index)
+
+ username = input("\nUsername [default=admin]: \n")
+ if username == '':
+ username = 'admin'
+
+ password = input("\nPassword [default=admin]: \n")
+ if password == '':
+ password = 'admin'
+
+ config = {'ip':ip,'port':port,'index':index,'username':username,'password':password}
+
+ store = input("\nDo you want to store these settings for future use? (y/n) [default=n] \n")
+ if store == '':
+ store = 'n'
+
+ while store != 'y' and store != 'n':
+ store = input("\nInvalid option.\n Do you want to store these settings for future use? (y/n) \n")
+ if store == 'y':
+ with open('DIS_Settings.json', 'w') as configFile:
+ json.dump(config, configFile)
+ return config
+
+def injectEvents(generator):
+ config = verifySettings()
+ instance = OpenSearch([{'host':config['ip'],'port':config['port']}], http_auth=(config['username'], config['password']), use_ssl=True, verify_certs=False)
+
+ if not instance.ping():
+ print('\nError: Could not connect to the indexer')
+ return
+
+ if (verifyIndex(config['index'],instance)):
+ print('\nTrying to inject the generated data...\n')
+ try:
+ helpers.bulk(instance, generator, index=config['index'])
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return
+
+
+def main():
+ action = input("Do you want to inject data or save it to a file? (i/s) [default=i]\n")
+ if action == '':
+ action = 'i'
+
+ while(action != 'i' and action != 's'):
+ action = input("\nInvalid option.\n Do you want to inject data or save it to a file? (i/s) \n")
+ number = input("\nHow many agents do you want to generate? [default=20]\n")
+ if number == '':
+ number = '20'
+ while(not number.isdigit()):
+ number = input("Invalid option.\n How many events do you want to generate? \n")
+ data = generateRandomData(number)
+ if action == 's':
+ print('\nGenerating {} events...\n'.format(number))
+ outfile = open('generatedData.json','a')
+ for i in data:
+ json.dump(i, outfile)
+ outfile.write('\n')
+ outfile.close()
+ print('\nDone!\n')
+ else:
+ injectEvents(data)
+ return
+
+if __name__=="__main__":
+ main()
diff --git a/scripts/wazuh-fleet-commands-generator/readme.md b/scripts/wazuh-fleet-commands-generator/readme.md
new file mode 100644
index 0000000000..dd266a5a7c
--- /dev/null
+++ b/scripts/wazuh-fleet-commands-generator/readme.md
@@ -0,0 +1,18 @@
+# Agents and groups injector
+
+This script generates agents and groups and saves them to a file or injects them to a Wazuh-Indexer/Opensearch instance.
+
+# Files
+
+- `dataInjectScript.py`: The main script file
+- `DIS_Template.json`: If the script creates a new index in the instance, it will create it with this template. If this file doesn't exist, it will create it without templates.
+- `DIS_Settings.json`: The script can save the Indexer/Opensearch connection parameters. They will be stored in this file.
+- `generatedData.json`: If the script is told to save the data to a file, it save it to this file.
+
+# Usage
+
+1. Install the requirements with `pip install -r requirements.txt`. For some Operating Systems it will fail and suggest a different way to install it (`sudo pacman -S python-xyz`, `sudo apt install python-xyz`, etc.).
+ If the package is not found in this way, we can install it running `pip install -r requirements.txt --break-system-packages` (It is recommended to avoid this option if possible)
+
+2. Run the script with `python3 dataInjectScript.py`
+3. Follow the instructions that it will show on the console.
diff --git a/scripts/wazuh-fleet-commands-generator/requirements.txt b/scripts/wazuh-fleet-commands-generator/requirements.txt
new file mode 100644
index 0000000000..7e8ce75a5b
--- /dev/null
+++ b/scripts/wazuh-fleet-commands-generator/requirements.txt
@@ -0,0 +1 @@
+opensearch_py==2.4.2
diff --git a/scripts/wazuh-fleet-generator/DIS_Settings.json b/scripts/wazuh-fleet-generator/DIS_Settings.json
new file mode 100644
index 0000000000..9bcba7371e
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/DIS_Settings.json
@@ -0,0 +1,11 @@
+{
+ "ip": "127.0.0.1",
+ "port": "9200",
+ "username": "admin",
+ "password": "admin",
+ "indexFleet": "fleet-agents-default",
+ "indexInventorySystem": "inventory-system-default",
+ "indexInventoryNetworks": "inventory-networks-default",
+ "indexInventoryProcesses": "inventory-processes-default",
+ "indexInventoryPackages": "inventory-packages-default"
+}
diff --git a/scripts/wazuh-fleet-generator/DIS_TemplateFleetAgents.json b/scripts/wazuh-fleet-generator/DIS_TemplateFleetAgents.json
new file mode 100644
index 0000000000..aabe4ae16c
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/DIS_TemplateFleetAgents.json
@@ -0,0 +1,92 @@
+{
+ "index_patterns": ["inventory-system-default"],
+ "template": {
+ "mappings": {
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "agent": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "network": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "MAC": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtu": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "wazuh": {
+ "properties": {
+ "cluster": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "manager": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "codec": "best_compression",
+ "mapping": {
+ "total_fields": {
+ "limit": 1000
+ }
+ },
+ "refresh_interval": "2s"
+ }
+ }
+ }
+}
diff --git a/scripts/wazuh-fleet-generator/DIS_TemplateInventoryNetworks.json b/scripts/wazuh-fleet-generator/DIS_TemplateInventoryNetworks.json
new file mode 100644
index 0000000000..2a45fb8793
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/DIS_TemplateInventoryNetworks.json
@@ -0,0 +1,89 @@
+{
+ "mappings": {
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "agent": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "network": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "MAC": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtu": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "wazuh": {
+ "properties": {
+ "cluster": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "manager": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "codec": "best_compression",
+ "mapping": {
+ "total_fields": {
+ "limit": 1000
+ }
+ },
+ "refresh_interval": "2s"
+ }
+ }
+}
diff --git a/scripts/wazuh-fleet-generator/DIS_TemplateInventorySystem.json b/scripts/wazuh-fleet-generator/DIS_TemplateInventorySystem.json
new file mode 100644
index 0000000000..74ca74c06a
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/DIS_TemplateInventorySystem.json
@@ -0,0 +1,120 @@
+{
+ "mappings": {
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "agent": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "host": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "checksum": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scan_time": {
+ "type": "date"
+ },
+ "sysname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "full": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "wazuh": {
+ "properties": {
+ "cluster": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "manager": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "codec": "best_compression",
+ "mapping": {
+ "total_fields": {
+ "limit": 1000
+ }
+ },
+ "refresh_interval": "2s"
+ }
+ }
+}
diff --git a/scripts/wazuh-fleet-generator/dataInjectScript.py b/scripts/wazuh-fleet-generator/dataInjectScript.py
new file mode 100644
index 0000000000..a643c8c139
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/dataInjectScript.py
@@ -0,0 +1,293 @@
+from datetime import timedelta, datetime
+from opensearchpy import OpenSearch, helpers
+import random
+import json
+import os.path
+import warnings
+import uuid
+
+warnings.filterwarnings("ignore")
+def generateRandomDate(days_interval=10):
+ start_date = datetime.now()
+ end_date = start_date - timedelta(days=days_interval)
+ random_date = start_date + (end_date - start_date) * random.random()
+ return(random_date.strftime("%Y-%m-%dT%H:%M:%S.{}Z".format(random.randint(0, 999))))
+
+def generateRandomGroups():
+ groups = ['default', 'group1', 'group2', 'group3']
+ num_groups = random.randint(1, len(groups))
+ return random.sample(groups, num_groups)
+
+def generateNodeNames():
+ nodes = ['wazuh', 'node01', 'node02', 'node03', 'node04', 'node05']
+ return nodes
+
+def generateRandomAgent(i):
+ agent={}
+ agent['build'] = {'original':'build{}'.format(random.randint(0, 9999))}
+ agent['id'] = str(uuid.uuid4())
+ agent['name'] = 'Agent_{}'.format(i)
+ agent['version'] = 'v5.0.0'
+ agent['ephemeral_id'] = '{}'.format(random.randint(0, 99999))
+ agent['groups'] = generateRandomGroups()
+ agent['node_name'] = random.choice(generateNodeNames())
+ agent['last_login'] = generateRandomDate()
+ return(agent)
+
+def generateRandomHost():
+ host = {}
+
+ os_versions = [
+ "Microsoft Windows 10 Home",
+ "Microsoft Windows 10 Pro",
+ "Microsoft Windows 11 Home",
+ "Microsoft Windows 11 Pro",
+ "macOS 10.15 Catalina",
+ "macOS 11 Big Sur",
+ "macOS 12 Monterey",
+ "Ubuntu 18.04 LTS",
+ "Ubuntu 20.04 LTS",
+ "Ubuntu 22.04 LTS",
+ "CentOS 7",
+ "CentOS 8",
+ "Red Hat Enterprise Linux 7",
+ "Red Hat Enterprise Linux 8",
+ "Debian 9 Stretch",
+ "Debian 10 Buster",
+ "Debian 11 Bullseye",
+ "Fedora 34",
+ "Fedora 35",
+ "Fedora 36",
+ "Arch Linux",
+ "OpenSUSE Leap 15.3",
+ "OpenSUSE Leap 15.4",
+ ]
+
+ platform_map = {
+ "Microsoft Windows": "windows",
+ "macOS": "darwin",
+ "Ubuntu": "linux",
+ "CentOS": "linux",
+ "Red Hat Enterprise Linux": "linux",
+ "Debian": "linux",
+ "Fedora": "linux",
+ "Arch Linux": "linux",
+ "OpenSUSE": "linux",
+ }
+
+ os = random.choice(os_versions)
+ platform_key = next(key for key in platform_map if os.startswith(key))
+ platform = platform_map[platform_key]
+
+ host['architecture'] = random.choice(["x86_64", "arm64", "i386"])
+ host['checksum'] = str(uuid.uuid4()).replace('-', '')[:32]
+ host['hostname'] = f"host-{random.randint(1000, 9999)}"
+ host['os'] = {
+ 'name': os.split(' ')[0],
+ 'platform': platform,
+ 'version': os.split(' ')[-1],
+ 'kernel': f"kernel-{random.randint(1, 10)}.{random.randint(0, 9)}.{random.randint(0, 99)}",
+ 'full': os,
+ 'type': platform_key.lower()
+ }
+ host['scan_time'] = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")
+ host['sysname'] = platform_key.lower()
+ host['ip'] = f"192.168.{random.randint(0, 255)}.{random.randint(0, 255)}"
+ return(host)
+
+def generateRandomNetwork():
+ network_names = ["docker0", "enp3s0", "virbr0", "wlp2s0", "eth0", "eth1", "lo", "br-3a2df5e3b74a"]
+ states = ["up", "down"]
+ types = ["ethernet", "wifi", "bridge", "loopback"]
+
+ network = {}
+ network['name'] = random.choice(network_names)
+ network['mac'] = ':'.join(['{:02x}'.format(random.randint(0, 255)) for _ in range(6)])
+ network['state'] = random.choice(states)
+ network['mtu'] = str(random.choice([1500, 9000, 1450, 1400]))
+ network['type'] = random.choice(types)
+ return (network)
+
+def generateRandomWazuh():
+ wazuh = {}
+ wazuh['cluster'] = {'name':random.choice(['wazuh.manager', 'wazuh']), 'node':random.choice(['master','worker-01','worker-02','worker-03'])}
+ return(wazuh)
+
+def generateFleetData(number):
+ for i in range(0, int(number)):
+ yield{
+ 'agent':generateRandomAgent(i),
+ 'host': generateRandomHost(),
+ 'wazuh':generateRandomWazuh()
+ }
+
+# def generateSystemData(fleetData):
+# for agent in fleetData:
+# yield{
+# 'agent':{
+# 'id':agent['agent']['id']
+# },
+# 'host':generateRandomHost(),
+# 'wazuh':agent['wazuh']
+# }
+
+def generateNetworksData(fleetData, number):
+ for agent in fleetData:
+ for i in range(0, int(number)):
+ yield{
+ 'agent':{
+ 'id':agent['agent']['id']
+ },
+ 'network':generateRandomNetwork(),
+ 'wazuh':agent['wazuh']
+ }
+
+def verifyIndex(index,instance,entity):
+ if not instance.indices.exists(index):
+ if os.path.exists(f'DIS_Template{entity}.json'):
+ print(f'\nIndex {index} does not exist. Trying to create it with the template in DIS_Template{entity}.json'.format(index))
+ with open(f'DIS_Template{entity}.json') as templateFile:
+ template = json.load(templateFile)
+ try:
+ instance.indices.create(index=index, body=template)
+ print('Done!')
+ except Exception as e:
+ print('Error: {}'.format(e))
+ else:
+ notemplate=input('\nIndex {} does not exist. Template file not found. Continue without template? (y/n)'.format(index))
+ while notemplate != 'y' and notemplate != 'n':
+ notemplate=input('\nInvalid option. Continue without template? (y/n) \n')
+ if notemplate == 'n':
+ return False
+ print('\nTrying to create index {} without template'.format(index))
+ try:
+ instance.indices.create(index=index)
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return True
+
+def verifySettings():
+ verified = False
+ if os.path.exists('DIS_Settings.json'):
+ with open('DIS_Settings.json') as configFile:
+ config = json.load(configFile)
+ if 'ip' not in config or 'port' not in config or 'indexFleet' not in config or 'indexInventorySystem' not in config or 'indexInventoryNetworks' not in config or 'indexInventoryProcesses' not in config or 'indexInventoryPackages' not in config or 'username' not in config or 'password' not in config:
+ print('\nDIS_Settings.json is not properly configured. Continuing without it.')
+ else:
+ verified = True
+ else:
+ print('\nDIS_Settings.json not found. Continuing without it.')
+
+ if not verified:
+ ip = input("\nEnter the IP of your Indexer [default=0.0.0.0]: \n")
+ if ip == '':
+ ip = '0.0.0.0'
+
+ port = input("\nEnter the port of your Indexer [default=9200]: \n")
+ if port == '':
+ port = '9200'
+
+ indexFleet = input("\nEnter the index name for the fleet [default=fleet-agents-default]: \n")
+ if indexFleet == '':
+ indexFleet = 'fleet-agents-default'
+
+ indexInventorySystem = input("\nEnter the index name for the inventory system [default=inventory-system-default]: \n")
+ if indexInventorySystem == '':
+ indexInventorySystem = 'inventory-system-default'
+
+ indexInventoryNetworks = input("\nEnter the index name for the inventory networks [default=inventory-networks-default]: \n")
+ if indexInventoryNetworks == '':
+ indexInventoryNetworks = 'inventory-networks-default'
+
+ indexInventoryProcesses = input("\nEnter the index name for the inventory processes [default=inventory-processes-default]: \n")
+ if indexInventoryProcesses == '':
+ indexInventoryProcesses = 'inventory-processes-default'
+
+ indexInventoryPackages = input("\nEnter the index name for the inventory packages [default=inventory-packages-default]: \n")
+ if indexInventoryPackages == '':
+ indexInventoryPackages = 'inventory-packages-default'
+
+ username = input("\nUsername [default=admin]: \n")
+ if username == '':
+ username = 'admin'
+
+ password = input("\nPassword [default=admin]: \n")
+ if password == '':
+ password = 'admin'
+
+ config = {'ip':ip,'port':port,'indexFleet':indexFleet, 'indexInventorySystem':indexInventorySystem, 'indexInventoryNetworks':indexInventoryNetworks, 'indexInventoryProcesses':indexInventoryProcesses, 'indexInventoryPackages':indexInventoryPackages, 'username':username,'password':password}
+
+ store = input("\nDo you want to store these settings for future use? (y/n) [default=n] \n")
+ if store == '':
+ store = 'n'
+
+ while store != 'y' and store != 'n':
+ store = input("\nInvalid option.\n Do you want to store these settings for future use? (y/n) \n")
+ if store == 'y':
+ with open('DIS_Settings.json', 'w') as configFile:
+ json.dump(config, configFile)
+ return config
+
+# def injectEvents(fleetData, systemData, networksData, processesData, packagesData):
+def injectEvents(fleetData, networksData):
+ config = verifySettings()
+ instance = OpenSearch([{'host':config['ip'],'port':config['port']}], http_auth=(config['username'], config['password']), use_ssl=True, verify_certs=False)
+
+ if not instance.ping():
+ print('\nError: Could not connect to the indexer')
+ return
+
+ if (verifyIndex(config['indexFleet'],instance, 'FleetAgents')):
+ print('\nTrying to inject the fleet generated data...\n')
+ try:
+ helpers.bulk(instance, fleetData, index=config['indexFleet'])
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+
+ # if (verifyIndex(config['indexInventorySystem'],instance, 'InventorySystem')):
+ # print('\nTrying to inject the system generated data...\n')
+ # try:
+ # helpers.bulk(instance, systemData, index=config['indexInventorySystem'])
+ # print('\nDone!')
+ # except Exception as e:
+ # print('\nError: {}'.format(e))
+
+ if (verifyIndex(config['indexInventoryNetworks'],instance, 'InventoryNetworks')):
+ print('\nTrying to inject the networks generated data...\n')
+ try:
+ helpers.bulk(instance, networksData, index=config['indexInventoryNetworks'])
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+
+ return
+
+
+def main():
+ numberAgents = input("\nHow many agents do you want to generate? [default=20]\n")
+ if numberAgents == '':
+ numberAgents = '20'
+ while(not numberAgents.isdigit()):
+ numberAgents = input("Invalid option.\n How many agents do you want to generate? \n")
+
+ numberNetworksAgent = input("\nHow many networks by agent do you want to generate? [default=5]\n")
+ if numberNetworksAgent == '':
+ numberNetworksAgent = '5'
+ while(not numberNetworksAgent.isdigit()):
+ numberNetworksAgent = input("Invalid option.\n How many networks by agent do you want to generate? \n")
+
+ fleetData = generateFleetData(numberAgents)
+ fleetList = list(fleetData)
+ # systemData = generateSystemData(fleetList)
+ networksData = generateNetworksData(fleetList, numberNetworksAgent)
+ # processesData = generateProcessesData(fleetData)
+ # packagesData = generatePackagesData(fleetData)
+
+ # injectEvents(fleetData, systemData, networksData, processesData, packagesData)
+ injectEvents(fleetList, networksData)
+ return
+
+if __name__=="__main__":
+ main()
diff --git a/scripts/wazuh-fleet-generator/readme.md b/scripts/wazuh-fleet-generator/readme.md
new file mode 100644
index 0000000000..dd266a5a7c
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/readme.md
@@ -0,0 +1,18 @@
+# Agents and groups injector
+
+This script generates agents and groups and saves them to a file or injects them to a Wazuh-Indexer/Opensearch instance.
+
+# Files
+
+- `dataInjectScript.py`: The main script file
+- `DIS_Template.json`: If the script creates a new index in the instance, it will create it with this template. If this file doesn't exist, it will create it without templates.
+- `DIS_Settings.json`: The script can save the Indexer/Opensearch connection parameters. They will be stored in this file.
+- `generatedData.json`: If the script is told to save the data to a file, it save it to this file.
+
+# Usage
+
+1. Install the requirements with `pip install -r requirements.txt`. For some Operating Systems it will fail and suggest a different way to install it (`sudo pacman -S python-xyz`, `sudo apt install python-xyz`, etc.).
+ If the package is not found in this way, we can install it running `pip install -r requirements.txt --break-system-packages` (It is recommended to avoid this option if possible)
+
+2. Run the script with `python3 dataInjectScript.py`
+3. Follow the instructions that it will show on the console.
diff --git a/scripts/wazuh-fleet-generator/requirements.txt b/scripts/wazuh-fleet-generator/requirements.txt
new file mode 100644
index 0000000000..7e8ce75a5b
--- /dev/null
+++ b/scripts/wazuh-fleet-generator/requirements.txt
@@ -0,0 +1 @@
+opensearch_py==2.4.2
diff --git a/scripts/wazuh-fleet-groups-generator/DIS_Settings.json b/scripts/wazuh-fleet-groups-generator/DIS_Settings.json
new file mode 100644
index 0000000000..c7809e0c2a
--- /dev/null
+++ b/scripts/wazuh-fleet-groups-generator/DIS_Settings.json
@@ -0,0 +1,7 @@
+{
+ "ip": "127.0.0.1",
+ "port": "9200",
+ "username": "admin",
+ "password": "admin",
+ "index": "wazuh-fleet-groups-default"
+}
diff --git a/scripts/wazuh-fleet-groups-generator/DIS_Template.json b/scripts/wazuh-fleet-groups-generator/DIS_Template.json
new file mode 100644
index 0000000000..5afa1f5416
--- /dev/null
+++ b/scripts/wazuh-fleet-groups-generator/DIS_Template.json
@@ -0,0 +1,69 @@
+{
+ "mappings": {
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "agents": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "wazuh": {
+ "properties": {
+ "cluster": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "manager": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "codec": "best_compression",
+ "mapping": {
+ "total_fields": {
+ "limit": 1000
+ }
+ },
+ "refresh_interval": "2s"
+ }
+ }
+}
diff --git a/scripts/wazuh-fleet-groups-generator/dataInjectScript.py b/scripts/wazuh-fleet-groups-generator/dataInjectScript.py
new file mode 100644
index 0000000000..141bb368ac
--- /dev/null
+++ b/scripts/wazuh-fleet-groups-generator/dataInjectScript.py
@@ -0,0 +1,186 @@
+from datetime import timedelta, datetime
+from opensearchpy import OpenSearch, helpers
+import random
+import json
+import os.path
+import warnings
+import uuid
+
+warnings.filterwarnings("ignore")
+def generateRandomDate(days_interval=10):
+ start_date = datetime.now()
+ end_date = start_date - timedelta(days=days_interval)
+ random_date = start_date + (end_date - start_date) * random.random()
+ return(random_date.strftime("%Y-%m-%dT%H:%M:%S.{}Z".format(random.randint(0, 999))))
+
+def generateRandomGroups():
+ groups = ['default', 'group1', 'group2', 'group3', 'group4', 'group5']
+ return groups
+
+def generateNodeNames():
+ nodes = ['wazuh', 'node1', 'node2', 'node3', 'node4', 'node5']
+ return nodes
+
+def generateRandomUUID():
+ return str(uuid.uuid4())
+
+def generateRandomAgent():
+ agent={}
+ agent['build'] = {'original':'build{}'.format(random.randint(0, 9999))}
+ agent['id'] = '00{}'.format(random.randint(1, 99))
+ agent['name'] = 'Agent{}'.format(random.randint(0, 99))
+ agent['version'] = 'v{}-stable'.format(random.randint(0, 9))
+ agent['ephemeral_id'] = '{}'.format(random.randint(0, 99999))
+ agent['groups'] = random.sample(generateRandomGroups(), random.randint(1, len(generateRandomGroups())))
+ agent['node_name'] = random.choice(generateNodeNames())
+ agent['created'] = generateRandomDate()
+ return(agent)
+
+def generateRandomHost():
+ host = {}
+ family=random.choice(['debian','ubuntu','macos','ios','android','RHEL'])
+ version='{}.{}'.format(random.randint(0, 99),random.randint(0, 99))
+ host['os'] = {
+ 'family': family,
+ 'full': family + ' ' + version,
+ 'kernel': version+'kernel{}'.format(random.randint(0, 99)),
+ 'name': family + ' ' + version,
+ 'platform': family,
+ 'type': random.choice(['windows','linux','macos','ios','android','unix']),
+ 'version': version
+ }
+ return(host)
+
+def generateRandomWazuh():
+ wazuh = {}
+ wazuh['cluster'] = {'name':random.choice(['wazuh.manager', 'wazuh']), 'node':random.choice(['master','worker-01','worker-02','worker-03'])}
+ return(wazuh)
+
+def generateRandomData(number):
+ for i in range(0, int(number)):
+ yield {
+ 'id': generateRandomUUID(),
+ 'name': 'default' if i == 0 else 'group{}'.format(i),
+ 'agents': '{}'.format(random.randint(0, 10)),
+ 'wazuh': generateRandomWazuh()
+ }
+
+def verifyIndex(index,instance):
+ if not instance.indices.exists(index):
+ if os.path.exists('DIS_Template.json'):
+ print('\nIndex {} does not exist. Trying to create it with the template in DIS_Template.json'.format(index))
+ with open('DIS_Template.json') as templateFile:
+ template = json.load(templateFile)
+ try:
+ instance.indices.create(index=index, body=template)
+ indexExist = True
+ print('Done!')
+ except Exception as e:
+ print('Error: {}'.format(e))
+ else:
+ notemplate=input('\nIndex {} does not exist. Template file not found. Continue without template? (y/n)'.format(index))
+ while notemplate != 'y' and notemplate != 'n':
+ notemplate=input('\nInvalid option. Continue without template? (y/n) \n')
+ if notemplate == 'n':
+ return False
+ print('\nTrying to create index {} without template'.format(index))
+ try:
+ instance.indices.create(index=index)
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return True
+
+def verifySettings():
+ verified = False
+ if os.path.exists('DIS_Settings.json'):
+ with open('DIS_Settings.json') as configFile:
+ config = json.load(configFile)
+ if 'ip' not in config or 'port' not in config or 'index' not in config or 'username' not in config or 'password' not in config:
+ print('\nDIS_Settings.json is not properly configured. Continuing without it.')
+ else:
+ verified = True
+ else:
+ print('\nDIS_Settings.json not found. Continuing without it.')
+
+ if not verified:
+ ip = input("\nEnter the IP of your Indexer [default=0.0.0.0]: \n")
+ if ip == '':
+ ip = '0.0.0.0'
+
+ port = input("\nEnter the port of your Indexer [default=9200]: \n")
+ if port == '':
+ port = '9200'
+
+ index = input("\nEnter the index name [default=wazuh-fleet-groups-default]: \n")
+ if index == '':
+ index = 'wazuh-fleet-groups-default'
+
+ url = 'https://{}:{}/{}/_doc'.format(ip, port, index)
+
+ username = input("\nUsername [default=admin]: \n")
+ if username == '':
+ username = 'admin'
+
+ password = input("\nPassword [default=admin]: \n")
+ if password == '':
+ password = 'admin'
+
+ config = {'ip':ip,'port':port,'index':index,'username':username,'password':password}
+
+ store = input("\nDo you want to store these settings for future use? (y/n) [default=n] \n")
+ if store == '':
+ store = 'n'
+
+ while store != 'y' and store != 'n':
+ store = input("\nInvalid option.\n Do you want to store these settings for future use? (y/n) \n")
+ if store == 'y':
+ with open('DIS_Settings.json', 'w') as configFile:
+ json.dump(config, configFile)
+ return config
+
+def injectEvents(generator):
+ config = verifySettings()
+ instance = OpenSearch([{'host':config['ip'],'port':config['port']}], http_auth=(config['username'], config['password']), use_ssl=True, verify_certs=False)
+
+ if not instance.ping():
+ print('\nError: Could not connect to the indexer')
+ return
+
+ if (verifyIndex(config['index'],instance)):
+ print('\nTrying to inject the generated data...\n')
+ try:
+ helpers.bulk(instance, generator, index=config['index'])
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return
+
+
+def main():
+ action = input("Do you want to inject data or save it to a file? (i/s) [default=i]\n")
+ if action == '':
+ action = 'i'
+
+ while(action != 'i' and action != 's'):
+ action = input("\nInvalid option.\n Do you want to inject data or save it to a file? (i/s) \n")
+ number = input("\nHow many agents do you want to generate? [default=20]\n")
+ if number == '':
+ number = '20'
+ while(not number.isdigit()):
+ number = input("Invalid option.\n How many events do you want to generate? \n")
+ data = generateRandomData(number)
+ if action == 's':
+ print('\nGenerating {} events...\n'.format(number))
+ outfile = open('generatedData.json','a')
+ for i in data:
+ json.dump(i, outfile)
+ outfile.write('\n')
+ outfile.close()
+ print('\nDone!\n')
+ else:
+ injectEvents(data)
+ return
+
+if __name__=="__main__":
+ main()
diff --git a/scripts/wazuh-fleet-groups-generator/readme.md b/scripts/wazuh-fleet-groups-generator/readme.md
new file mode 100644
index 0000000000..dd266a5a7c
--- /dev/null
+++ b/scripts/wazuh-fleet-groups-generator/readme.md
@@ -0,0 +1,18 @@
+# Agents and groups injector
+
+This script generates agents and groups and saves them to a file or injects them to a Wazuh-Indexer/Opensearch instance.
+
+# Files
+
+- `dataInjectScript.py`: The main script file
+- `DIS_Template.json`: If the script creates a new index in the instance, it will create it with this template. If this file doesn't exist, it will create it without templates.
+- `DIS_Settings.json`: The script can save the Indexer/Opensearch connection parameters. They will be stored in this file.
+- `generatedData.json`: If the script is told to save the data to a file, it save it to this file.
+
+# Usage
+
+1. Install the requirements with `pip install -r requirements.txt`. For some Operating Systems it will fail and suggest a different way to install it (`sudo pacman -S python-xyz`, `sudo apt install python-xyz`, etc.).
+ If the package is not found in this way, we can install it running `pip install -r requirements.txt --break-system-packages` (It is recommended to avoid this option if possible)
+
+2. Run the script with `python3 dataInjectScript.py`
+3. Follow the instructions that it will show on the console.
diff --git a/scripts/wazuh-fleet-groups-generator/requirements.txt b/scripts/wazuh-fleet-groups-generator/requirements.txt
new file mode 100644
index 0000000000..7e8ce75a5b
--- /dev/null
+++ b/scripts/wazuh-fleet-groups-generator/requirements.txt
@@ -0,0 +1 @@
+opensearch_py==2.4.2