Skip to content

Commit

Permalink
Add upgrade agents global action (#6501)
Browse files Browse the repository at this point in the history
* Add upgrade agents global action

* Add Upgrade result modal

* Update CHANGELOG

* Minor fixes

* Add unit tests

* Improve upgrade progress message

* Remove wazuh-endpoints plugin

* Fix file name

* Fix import

* Fix get tasks status for readonly user

* Improve return condition

* Improve error message for Timeout status

* Add tooltipo to timeout status

* Modularize get status functions

* Fix status name and key values

* Separate upgrade service in two services
  • Loading branch information
lucianogorza authored Mar 19, 2024
1 parent be39ae4 commit 0760cdd
Show file tree
Hide file tree
Showing 53 changed files with 1,436 additions and 1,068 deletions.
4 changes: 1 addition & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145)
- Added a migration task to setup the configuration using a configuration file [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337)
- Added the ability to manage the API hosts from the Server APIs [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) [#6519](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6519)
- Added edit groups action to Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250)
- Added upgrade agent action to Endpoints Summary [#6476](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6476)
- Added global actions add agents to groups and remove agents from groups to Endpoints Summary [#6274](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6274)
- Added edit agent groups and upgrade agents actions to Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250) [#6476](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6476) [#6274](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6274) [#6501](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6501)
- Added propagation of updates from the table to dashboard visualizations in Endpoints summary [#6460](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6460)

### Changed
Expand Down
1 change: 0 additions & 1 deletion docker/osd-dev/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ 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-endpoints:/home/node/kbn/plugins/wazuh-endpoints'
- 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
Expand Down
9 changes: 9 additions & 0 deletions plugins/main/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,23 @@ export const API_NAME_TASK_STATUS = {
DONE: 'Done',
IN_PROGRESS: 'In progress',
FAILED: 'Failed',
TIMEOUT: 'Timeout',
} as const;

export const UI_TASK_STATUS = [
API_NAME_TASK_STATUS.DONE,
API_NAME_TASK_STATUS.IN_PROGRESS,
API_NAME_TASK_STATUS.FAILED,
API_NAME_TASK_STATUS.TIMEOUT,
];

export const UI_TASK_STATUS_COLORS = {
[API_NAME_TASK_STATUS.DONE]: 'success',
[API_NAME_TASK_STATUS.IN_PROGRESS]: 'warning',
[API_NAME_TASK_STATUS.FAILED]: 'danger',
[API_NAME_TASK_STATUS.TIMEOUT]: 'subdued',
};

// Documentation
export const DOCUMENTATION_WEB_BASE_URL = 'https://documentation.wazuh.com';

Expand Down
12 changes: 10 additions & 2 deletions plugins/main/public/components/common/tables/table-wz-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const getFilters = filters => {

export function TableWzAPI({
actionButtons,
postActionButtons,
addOnTitle,
extra,
setReload,
Expand All @@ -53,6 +54,11 @@ export function TableWzAPI({
| ReactNode
| ReactNode[]
| (({ filters }: { filters }) => ReactNode);
postActionButtons?:
| ReactNode
| ReactNode[]
| (({ filters }: { filters }) => ReactNode);

title?: string;
addOnTitle?: ReactNode;
description?: string;
Expand Down Expand Up @@ -147,7 +153,7 @@ export function TableWzAPI({
},
[]);

const renderActionButtons = filters => {
const renderActionButtons = (actionButtons, filters) => {
if (Array.isArray(actionButtons)) {
return actionButtons.map((button, key) => (
<EuiFlexItem key={key} grow={false}>
Expand Down Expand Up @@ -216,7 +222,7 @@ export function TableWzAPI({
<EuiFlexItem grow={false}>
<EuiFlexGroup wrap alignItems={'center'} responsive={false}>
{/* Render optional custom action button */}
{renderActionButtons(filters)}
{renderActionButtons(actionButtons, filters)}
{/* Render optional reload button */}
{rest.showReload && ReloadButton}
{/* Render optional export to CSV button */}
Expand All @@ -232,6 +238,8 @@ export function TableWzAPI({
}
/>
)}
{/* Render optional post custom action button */}
{renderActionButtons(postActionButtons, filters)}
{rest.showFieldSelector && (
<EuiFlexItem grow={false}>
<EuiToolTip content='Select visible fields' position='left'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ const OutdatedAgentsCard = ({
}
description={
<EuiTextColor color={contentType}>
<small>Agents</small>
<small>{outdatedAgents === 1 ? 'Agent' : 'Agents'}</small>
</EuiTextColor>
}
titleColor='danger'
titleColor='warning'
isLoading={isLoading}
titleSize='l'
textAlign='center'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,14 @@
width: 100%;
display: grid;
grid-template-columns: 1fr;
gap: 20px 10px;
gap: 16px;
min-height: 200px;

@media (min-width: 1024px) {
grid-template-columns: 1fr 1fr;
}

@media (min-width: 1440px) {
gap: 10px;
grid-template-columns:
minmax(375px, 1fr) minmax(375px, 1fr) minmax(375px, 1fr)
minmax(150px, 300px);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,68 +18,83 @@ export const useGetUpgradeTasks = (reload: any) => {
const [getErrorIsLoading, setErrorIsLoading] = useState(true);
const [getErrorTasksError, setGetErrorTasksError] = useState();

const [totalTimeoutUpgradeTasks, setTotalTimeoutUpgradeTasks] =
useState<number>(0);
const [getTimeoutIsLoading, setTimeoutIsLoading] = useState(true);
const [getTimeoutError, setGetTimeoutError] = useState();

const datetime = new Date();
datetime.setMinutes(datetime.getMinutes() - beforeMinutes);
const formattedDate = datetime.toISOString();
const timeFilter = `last_update_time>${formattedDate}`;

const getUpgradesInProgress = async () => {
const getUpgradeStatus = async (
status: string,
setIsLoading: (isLoading: boolean) => void,
setTotalTasks: (totalTasks: number) => void,
setError: (error) => void,
q?: string,
) => {
try {
setGetInProgressIsLoading(true);
setIsLoading(true);
const { total_affected_items } = await getTasks({
status: API_NAME_TASK_STATUS.IN_PROGRESS,
status,
command: 'upgrade',
limit: 1,
q,
});
setTotalInProgressTasks(total_affected_items);
setGetInProgressError(undefined);
setTotalTasks(total_affected_items);
setError(undefined);
} catch (error: any) {
setGetInProgressError(error);
setError(error);
} finally {
setGetInProgressIsLoading(false);
setIsLoading(false);
}
};

const getUpgradesInProgress = async () =>
await getUpgradeStatus(
API_NAME_TASK_STATUS.IN_PROGRESS,
setGetInProgressIsLoading,
setTotalInProgressTasks,
setGetInProgressError,
);

const getUpgradesSuccess = async () => {
try {
setSuccessIsLoading(true);
const { total_affected_items } = await getTasks({
status: API_NAME_TASK_STATUS.DONE,
command: 'upgrade',
limit: 1,
q: timeFilter,
});
setTotalSuccessTasks(total_affected_items);
setGetSuccessError(undefined);
} catch (error: any) {
setGetSuccessError(error);
} finally {
setSuccessIsLoading(false);
}
await getUpgradeStatus(
API_NAME_TASK_STATUS.DONE,
setSuccessIsLoading,
setTotalSuccessTasks,
setGetSuccessError,
timeFilter,
);
};

const getUpgradesError = async () => {
try {
setErrorIsLoading(true);
const { total_affected_items } = await getTasks({
status: API_NAME_TASK_STATUS.FAILED,
command: 'upgrade',
limit: 1,
q: timeFilter,
});
setTotalErrorUpgradeTasks(total_affected_items);
setGetErrorTasksError(undefined);
} catch (error: any) {
setGetErrorTasksError(error);
} finally {
setErrorIsLoading(false);
}
await getUpgradeStatus(
API_NAME_TASK_STATUS.FAILED,
setErrorIsLoading,
setTotalErrorUpgradeTasks,
setGetErrorTasksError,
timeFilter,
);
};

const getUpgradeTimeout = async () => {
await getUpgradeStatus(
API_NAME_TASK_STATUS.TIMEOUT,
setTimeoutIsLoading,
setTotalTimeoutUpgradeTasks,
setGetTimeoutError,
timeFilter,
);
};

const fetchData = async () => {
await getUpgradesInProgress();
await getUpgradesSuccess();
await getUpgradesError();
await getUpgradeTimeout();
};

useEffect(() => {
Expand All @@ -104,5 +119,8 @@ export const useGetUpgradeTasks = (reload: any) => {
getErrorIsLoading,
totalErrorUpgradeTasks,
getErrorTasksError,
getTimeoutIsLoading,
totalTimeoutUpgradeTasks,
getTimeoutError,
};
};
2 changes: 1 addition & 1 deletion plugins/main/public/components/endpoints-summary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const MainEndpointsSummary = compose(
return (
<EuiEmptyPrompt
iconType='watchesApp'
title={<h2>No agents were added to this manager.</h2>}
title={<h2>No agents were added to the manager</h2>}
body={<p>Add agents to fleet to start monitoring</p>}
actions={
<WzButtonPermissions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import IApiResponse from '../../../react-services/interfaces/api-response.interface';
import { paginatedAgentsGroupService } from './paginated-agents-group';
import { paginatedAgentsRequestService } from './paginated-agents-request';

export const addAgentsToGroupService = async (parameters: {
agentIds: string[];
groupId: string;
pageSize?: number;
}): Promise<IApiResponse<string>> =>
await paginatedAgentsGroupService({ addOrRemove: 'add', ...parameters });
await paginatedAgentsRequestService({
method: 'PUT',
url: '/agents/group',
...parameters,
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { removeAgentsFromGroupService } from './remove-agents-from-group';
export { addAgentToGroupService } from './add-agent-to-group';
export { addAgentsToGroupService } from './add-agents-to-group';
export { getGroupsService } from './get-groups';
export { upgradeAgentService } from './upgrade-agent';
export { upgradeAgentsService } from './upgrade-agents';
export { getOutdatedAgents } from './get-outdated-agents';
export { getTasks } from './get-tasks';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { paginatedAgentsGroupService } from './paginated-agents-group';
import { paginatedAgentsRequestService } from './paginated-agents-request';
import { WzRequest } from '../../../react-services/wz-request';

jest.mock('../../../react-services/wz-request', () => ({
Expand All @@ -7,7 +7,7 @@ jest.mock('../../../react-services/wz-request', () => ({
},
}));

describe('paginatedAgentsGroupService', () => {
describe('paginatedAgentsRequestService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
Expand Down Expand Up @@ -46,13 +46,14 @@ describe('paginatedAgentsGroupService', () => {
);

const params = {
addOrRemove: 'add' as any,
method: 'PUT' as any,
url: '/agents/group',
agentIds: ['agent1', 'agent2', 'agent3'],
groupId: 'group1',
pageSize: 2,
};

const result = await paginatedAgentsGroupService(params);
const result = await paginatedAgentsRequestService(params);

expect(WzRequest.apiReq).toHaveBeenCalledWith(
'PUT',
Expand Down Expand Up @@ -146,13 +147,14 @@ describe('paginatedAgentsGroupService', () => {
);

const params = {
addOrRemove: 'add' as any,
method: 'PUT' as any,
url: '/agents/group',
agentIds: ['agent1', 'agent2', 'agent3'],
groupId: 'group1',
pageSize: 2,
};

const result = await paginatedAgentsGroupService(params);
const result = await paginatedAgentsRequestService(params);

expect(WzRequest.apiReq).toHaveBeenCalledWith(
'PUT',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ export type ErrorAgent = {
id: string[];
};

export const paginatedAgentsGroupService = async ({
addOrRemove,
export const paginatedAgentsRequestService = async ({
method,
url,
agentIds,
groupId,
pageSize = 1000,
}: {
addOrRemove: 'add' | 'remove';
method: 'PUT' | 'DELETE';
url: string;
agentIds: string[];
groupId: string;
groupId?: string;
pageSize?: number;
}): Promise<IApiResponse<string>> => {
}): Promise<IApiResponse<any>> => {
let offset = 0;
let requestAgentIds: string[] = [];
let allAffectedItems: string[] = [];
Expand All @@ -45,11 +47,11 @@ export const paginatedAgentsGroupService = async ({
message: responseMessage,
},
} = (await WzRequest.apiReq(
addOrRemove === 'add' ? 'PUT' : 'DELETE',
`/agents/group`,
method,
url,
{
params: {
group_id: groupId,
...(groupId ? { group_id: groupId } : {}),
agents_list: requestAgentIds.join(','),
wait_for_complete: true,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import IApiResponse from '../../../react-services/interfaces/api-response.interface';
import { paginatedAgentsGroupService } from './paginated-agents-group';
import { paginatedAgentsRequestService } from './paginated-agents-request';

export const removeAgentsFromGroupService = async (parameters: {
agentIds: string[];
groupId: string;
pageSize?: number;
}): Promise<IApiResponse<string>> =>
await paginatedAgentsGroupService({ addOrRemove: 'remove', ...parameters });
await paginatedAgentsRequestService({
method: 'DELETE',
url: '/agents/group',
...parameters,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import IApiResponse from '../../../react-services/interfaces/api-response.interface';
import { WzRequest } from '../../../react-services/wz-request';
import { ResponseUpgradeAgents } from '../types';

export const upgradeAgentService = async (agentId: string) =>
(await WzRequest.apiReq('PUT', '/agents/upgrade', {
params: {
agents_list: agentId,
wait_for_complete: true,
},
})) as IApiResponse<ResponseUpgradeAgents>;
Loading

0 comments on commit 0760cdd

Please sign in to comment.