mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Fleet] Add raw status to Agent details UI (#154826)
## Summary Make raw agent status discoverable in Fleet UI, under `Agent details` tab. Closes https://github.com/elastic/kibana/issues/154067 ### Screenshots <img width="1918" alt="Screenshot 2023-04-19 at 12 14 48" src="https://user-images.githubusercontent.com/23701614/233059955-7f066ad5-39cd-4685-b76b-41bc31ede4e8.png"> <img width="1918" alt="Screenshot 2023-04-19 at 13 04 06" src="https://user-images.githubusercontent.com/23701614/233059973-3f1b507f-d0bf-48ab-929b-c567f9814377.png"> ### UX checklist - [ ] Action link title (`View agent JSON`) - [ ] Flyout title (`{agentName} agent details`) - [ ] Download button - [ ] Download button label (`Download JSON`) - [ ] Downloaded file name (`{agentName}-agent-details.json`) ### Testing steps 1. Run Kibana in dev on this branch. 2. In Fleet, click on an agent to get to the agent details page. 3. There should be a new `View agent JSON` item in the `Actions` menu. Click it. 4. A new flyout should open with the agent details in JSON format. Clicking outside of the flyout or on the `Close` button should close the flyout. 5. The `Download JSON` button should download the JSON correctly. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
cd90b11e97
commit
b97b18eecd
3 changed files with 218 additions and 1 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useMemo } from 'react';
|
||||
import React, { memo, useState, useMemo, useCallback } from 'react';
|
||||
import { EuiPortal, EuiContextMenuItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
|
@ -24,6 +24,8 @@ import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services';
|
|||
import { AgentRequestDiagnosticsModal } from '../../components/agent_request_diagnostics_modal';
|
||||
import { ExperimentalFeaturesService } from '../../../../services';
|
||||
|
||||
import { AgentDetailsJsonFlyout } from './agent_details_json_flyout';
|
||||
|
||||
export const AgentDetailsActionMenu: React.FunctionComponent<{
|
||||
agent: Agent;
|
||||
agentPolicy?: AgentPolicy;
|
||||
|
@ -37,8 +39,17 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
|||
const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false);
|
||||
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
|
||||
const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = useState(false);
|
||||
const [isAgentDetailsJsonFlyoutOpen, setIsAgentDetailsJsonFlyoutOpen] = useState<boolean>(false);
|
||||
const isUnenrolling = agent.status === 'unenrolling';
|
||||
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
||||
const onContextMenuChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setIsContextMenuOpen(open);
|
||||
},
|
||||
[setIsContextMenuOpen]
|
||||
);
|
||||
|
||||
const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy);
|
||||
const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get();
|
||||
|
||||
|
@ -70,6 +81,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
|||
onClick={() => {
|
||||
setIsUnenrollModalOpen(true);
|
||||
}}
|
||||
key="unenrollAgent"
|
||||
>
|
||||
{isUnenrolling ? (
|
||||
<FormattedMessage
|
||||
|
@ -89,12 +101,26 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
|||
onClick={() => {
|
||||
setIsUpgradeModalOpen(true);
|
||||
}}
|
||||
key="upgradeAgent"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentList.upgradeOneButton"
|
||||
defaultMessage="Upgrade agent"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="inspect"
|
||||
onClick={() => {
|
||||
setIsContextMenuOpen(false);
|
||||
setIsAgentDetailsJsonFlyoutOpen(!isAgentDetailsJsonFlyoutOpen);
|
||||
}}
|
||||
key="agentDetailsJson"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentList.viewAgentDetailsJsonText"
|
||||
defaultMessage="View agent JSON"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
];
|
||||
|
||||
if (diagnosticFileUploadEnabled) {
|
||||
|
@ -105,6 +131,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
|||
onClick={() => {
|
||||
setIsRequestDiagnosticsModalOpen(true);
|
||||
}}
|
||||
key="requestDiagnostics"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentList.diagnosticsOneButton"
|
||||
|
@ -158,7 +185,17 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
|||
/>
|
||||
</EuiPortal>
|
||||
)}
|
||||
{isAgentDetailsJsonFlyoutOpen && (
|
||||
<EuiPortal>
|
||||
<AgentDetailsJsonFlyout
|
||||
agent={agent}
|
||||
onClose={() => setIsAgentDetailsJsonFlyoutOpen(false)}
|
||||
/>
|
||||
</EuiPortal>
|
||||
)}
|
||||
<ContextMenuActions
|
||||
isOpen={isContextMenuOpen}
|
||||
onChange={onContextMenuChange}
|
||||
button={{
|
||||
props: { iconType: 'arrowDown', iconSide: 'right', color: 'primary' },
|
||||
children: (
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import type { Agent } from '../../../../types';
|
||||
import { useStartServices } from '../../../../hooks';
|
||||
|
||||
import { AgentDetailsJsonFlyout } from './agent_details_json_flyout';
|
||||
|
||||
jest.mock('../../../../hooks');
|
||||
|
||||
const mockUseStartServices = useStartServices as jest.Mock;
|
||||
|
||||
describe('AgentDetailsJsonFlyout', () => {
|
||||
const agent: Agent = {
|
||||
id: '123',
|
||||
packages: [],
|
||||
type: 'PERMANENT',
|
||||
active: true,
|
||||
enrolled_at: `${Date.now()}`,
|
||||
user_provided_metadata: {},
|
||||
local_metadata: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseStartServices.mockReturnValue({
|
||||
docLinks: { links: { fleet: { troubleshooting: 'https://elastic.co' } } },
|
||||
});
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
return render(<AgentDetailsJsonFlyout agent={agent} onClose={jest.fn()} />);
|
||||
};
|
||||
|
||||
it('renders a title with the agent id if host name is not defined', () => {
|
||||
const result = renderComponent();
|
||||
expect(result.getByText("'123' agent details")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a title with the agent host name if defined', () => {
|
||||
agent.local_metadata = {
|
||||
host: {
|
||||
hostname: '456',
|
||||
},
|
||||
};
|
||||
const result = renderComponent();
|
||||
expect(result.getByText("'456' agent details")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not add a link to the page after clicking Download', () => {
|
||||
const result = renderComponent();
|
||||
const downloadButton = result.getByRole('button', { name: 'Download JSON' });
|
||||
const anchorMocked = {
|
||||
href: '',
|
||||
click: jest.fn(),
|
||||
download: '',
|
||||
setAttribute: jest.fn(),
|
||||
} as any;
|
||||
const createElementSpyOn = jest
|
||||
.spyOn(document, 'createElement')
|
||||
.mockReturnValueOnce(anchorMocked);
|
||||
|
||||
downloadButton.click();
|
||||
expect(createElementSpyOn).toBeCalledWith('a');
|
||||
expect(result.queryAllByRole('link')).toHaveLength(1); // The only link is the one from the flyout's description.
|
||||
expect(result.getByRole('link')).toHaveAttribute('href', 'https://elastic.co');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCodeBlock,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { Agent } from '../../../../types';
|
||||
import { useStartServices } from '../../../../hooks';
|
||||
|
||||
export const AgentDetailsJsonFlyout = memo<{ agent: Agent; onClose: () => void }>(
|
||||
({ agent, onClose }) => {
|
||||
const agentToJson = JSON.stringify(agent, null, 2);
|
||||
const agentName =
|
||||
typeof agent.local_metadata?.host?.hostname === 'string'
|
||||
? agent.local_metadata.host.hostname
|
||||
: agent.id;
|
||||
|
||||
const downloadJson = () => {
|
||||
const link = document.createElement('a');
|
||||
link.href = `data:text/json;charset=utf-8,${encodeURIComponent(agentToJson)}`;
|
||||
link.download = `${agentName}-agent-details.json`;
|
||||
link.click();
|
||||
};
|
||||
|
||||
const { docLinks } = useStartServices();
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="l" maxWidth={640}>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetails.jsonFlyoutTitle"
|
||||
defaultMessage="'{name}' agent details"
|
||||
values={{
|
||||
name: agentName,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetails.jsonFlyoutDescription"
|
||||
defaultMessage="The JSON below is the raw agent data tracked by Fleet. This data can be useful for debugging or troubleshooting Elastic Agent. For more information, see the {doc}."
|
||||
values={{
|
||||
doc: (
|
||||
<EuiLink href={docLinks.links.fleet.troubleshooting}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetails.jsonFlyoutDocLink"
|
||||
defaultMessage="troubleshooting documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiCodeBlock language="json">{agentToJson}</EuiCodeBlock>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={onClose} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetails.agentDetailsJsonFlyoutCloseButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="download" onClick={downloadJson}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetails.agentDetailsJsonDownloadButtonLabel"
|
||||
defaultMessage="Download JSON"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue