mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[UII] Add status tracking for agentless integrations (#199567)
## Summary Resolves https://github.com/elastic/ingest-dev/issues/3933. For deployments that support agentless, integrations with agentless deployment mode enabled will allow the status of agentless integration policies to be tracked. ### Key technical changes - A new field `supports_agentless` was added to package policies. This field already exists on agent policies. When an agentless integration is created, `supports_agentless: true` is now added to both the package policy and its parent agent policy. - This allows easier filtering for agentless integrations as we avoid having to retrieve & check against every parent agent policy. - This also means existing agentless policies do not get this new status tracking UI, only new ones created after this change. Since agentless is not yet GA, I think this is okay. - `/api/fleet/agent_status/data` now takes optional query params `pkgName` and `pkgVersion`. When both are specified, the API will check if agent(s) have ingested data for only that package's datastreams. ## UI walkthrough <details> <summary>🖼️ Click to show screenshots</summary> 1. **Integration policies** page now shows two tables for integrations meeting the above condition, one for agentless policies and one for agent-based policies:  2. Clicking the status badge in the agentless policies table opens a flyout with two steps: confirm agentless enrollment and confirm incoming data:  3. Confirm agentless enrollment polls for an agent enrolled into that integration policy's agent policy. If that agent is reporting an unhealthy status, the integration component UI is shown. This UI is the same one used on Fleet > Agents > Agent details page and shows all components reported by that agent:  4. Once a healthy agentless enrollment is established, confirm incoming data starts polling for data for that integration ingested by that agent ID in the past 5 minutes:  5. If data could not be retrieved in 5 minutes, an error message shows while polling continues in the background:  6. If data is retrieved, a success message is shown:  </details> ## Testing Easiest way to test is use the Cloud deployment from this PR. Enable Beta integrations and navigate to CSPM. Add a CSPM integration using `Agentless` setup technology. Then you can track the status of the agentless deployment on the Integrations policies tab. For local testing, the following is required to simulate agentless agent: 1. Add the following to kibana.dev.yml: ``` xpack.cloud.id: 'anything-to-pass-cloud-validation-checks' xpack.fleet.agentless.enabled: true xpack.fleet.agentless.api.url: 'https://localhost:8443' xpack.fleet.agentless.api.tls.certificate: './config/certs/ess-client.crt' xpack.fleet.agentless.api.tls.key: './config/certs/ess-client.key' xpack.fleet.agentless.api.tls.ca: './config/certs/ca.crt' ``` 2. Apply [this patch](https://gist.github.com/jen-huang/dfc3e02ceb63976ad54bd1f50c524cb4) to prevent attempt to create agentless pod 3. Enroll a Fleet Server as usual 4. Enable Beta integrations and navigate to CSPM. Add a CSPM integration using `Agentless` setup technology. 5. Enroll a normal Elastic Agent to the agent policy for that CSPM integration by using the token from Enrollment tokens ## To-do - [x] API tests - [x] Unit UI tests - [x] Manual Cloud tests - [x] File docs request - https://github.com/elastic/ingest-docs/issues/1466 - [ ] Update troubleshooting guide link once available ### Checklist Delete any items that are not applicable to this PR. - [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f2da55a5f4
commit
3188cda4e3
46 changed files with 2664 additions and 595 deletions
|
@ -6919,6 +6919,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -7943,6 +7949,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -8736,6 +8748,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -9790,6 +9808,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -10813,6 +10837,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -11607,6 +11637,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -12744,6 +12780,22 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "pkgName",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "pkgVersion",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "previewData",
|
||||
|
@ -30076,6 +30128,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -30563,6 +30621,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
|
@ -30800,6 +30864,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
|
@ -31336,6 +31406,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -32038,6 +32114,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -33210,6 +33292,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -33622,6 +33710,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
|
@ -34314,6 +34408,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -34808,6 +34908,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
|
@ -35044,6 +35150,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
|
@ -35579,6 +35691,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -6919,6 +6919,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -7943,6 +7949,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -8736,6 +8748,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -9790,6 +9808,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -10813,6 +10837,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -11607,6 +11637,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -12744,6 +12780,22 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "pkgName",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "pkgVersion",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "previewData",
|
||||
|
@ -30076,6 +30128,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -30563,6 +30621,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
|
@ -30800,6 +30864,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
|
@ -31336,6 +31406,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -32038,6 +32114,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -33210,6 +33292,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -33622,6 +33710,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
|
@ -34314,6 +34408,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -34808,6 +34908,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
|
@ -35044,6 +35150,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"vars": {
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
|
@ -35579,6 +35691,12 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"supports_agentless": {
|
||||
"default": false,
|
||||
"description": "Indicates whether the package policy belongs to an agentless agent policy.",
|
||||
"nullable": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -9909,6 +9909,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -10618,6 +10623,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -11167,6 +11177,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -11696,6 +11711,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -12404,6 +12424,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -12953,6 +12978,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -13891,6 +13921,16 @@ paths:
|
|||
type: string
|
||||
type: array
|
||||
- type: string
|
||||
- in: query
|
||||
name: pkgName
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: pkgVersion
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: previewData
|
||||
required: false
|
||||
|
@ -25537,6 +25577,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -25865,6 +25910,11 @@ paths:
|
|||
description: Agent policy IDs where that package policy will be added
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
|
@ -26015,6 +26065,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
|
@ -26366,6 +26421,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -26826,6 +26886,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -27325,6 +27390,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -27655,6 +27725,11 @@ paths:
|
|||
description: Agent policy IDs where that package policy will be added
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
|
@ -27804,6 +27879,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
|
@ -28154,6 +28234,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -28931,6 +29016,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -29210,6 +29300,11 @@ paths:
|
|||
description: Agent policy IDs where that package policy will be added
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
|
|
|
@ -12769,6 +12769,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -13477,6 +13482,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -14025,6 +14035,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -14553,6 +14568,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -15260,6 +15280,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -15808,6 +15833,11 @@ paths:
|
|||
required:
|
||||
- id
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -16739,6 +16769,16 @@ paths:
|
|||
type: string
|
||||
type: array
|
||||
- type: string
|
||||
- in: query
|
||||
name: pkgName
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: pkgVersion
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: previewData
|
||||
required: false
|
||||
|
@ -28320,6 +28360,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -28647,6 +28692,11 @@ paths:
|
|||
description: Agent policy IDs where that package policy will be added
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
|
@ -28797,6 +28847,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
|
@ -29148,6 +29203,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -29607,6 +29667,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -30104,6 +30169,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -30433,6 +30503,11 @@ paths:
|
|||
description: Agent policy IDs where that package policy will be added
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
|
@ -30582,6 +30657,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
|
@ -30932,6 +31012,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -31706,6 +31791,11 @@ paths:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
updated_at:
|
||||
type: string
|
||||
updated_by:
|
||||
|
@ -31985,6 +32075,11 @@ paths:
|
|||
description: Agent policy IDs where that package policy will be added
|
||||
type: string
|
||||
type: array
|
||||
supports_agentless:
|
||||
default: false
|
||||
description: Indicates whether the package policy belongs to an agentless agent policy.
|
||||
nullable: true
|
||||
type: boolean
|
||||
vars:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
|
|
|
@ -533,6 +533,7 @@
|
|||
"revision",
|
||||
"secret_references",
|
||||
"secret_references.id",
|
||||
"supports_agentless",
|
||||
"updated_at",
|
||||
"updated_by",
|
||||
"vars"
|
||||
|
@ -715,6 +716,7 @@
|
|||
"revision",
|
||||
"secret_references",
|
||||
"secret_references.id",
|
||||
"supports_agentless",
|
||||
"updated_at",
|
||||
"updated_by",
|
||||
"vars"
|
||||
|
|
|
@ -1786,6 +1786,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"supports_agentless": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "date"
|
||||
},
|
||||
|
@ -2374,6 +2377,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"supports_agentless": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "date"
|
||||
},
|
||||
|
|
|
@ -108,7 +108,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"fleet-agent-policies": "f57d3b70e4175a19a18f18ee72a379ceec82e1fc",
|
||||
"fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417",
|
||||
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
|
||||
"fleet-package-policies": "8be2cabfed89e103e0d413f2900e9cf6cd31bc68",
|
||||
"fleet-package-policies": "0206c20f27286787b91814a2e7872f06dc1e8e47",
|
||||
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
|
||||
"fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d",
|
||||
"fleet-setup-lock": "0dc784792c79b5af5a6e6b5dcac06b0dbaa90bde",
|
||||
|
@ -124,7 +124,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"ingest-agent-policies": "5e95e539826a40ad08fd0c1d161da0a4d86ffc6d",
|
||||
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
|
||||
"ingest-outputs": "55988d5f778bbe0e76caa7e6468707a0a056bdd8",
|
||||
"ingest-package-policies": "dfa7b1045a2667a822181f40f012786724492439",
|
||||
"ingest-package-policies": "60d43f475f91417d14d9df05476acf2e63e99435",
|
||||
"ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505",
|
||||
"inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83",
|
||||
"kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad",
|
||||
|
|
|
@ -72,6 +72,7 @@ export const PACKAGE_POLICIES_MAPPINGS = {
|
|||
properties: {},
|
||||
},
|
||||
secret_references: { properties: { id: { type: 'keyword' } } },
|
||||
supports_agentless: { type: 'boolean' },
|
||||
revision: { type: 'integer' },
|
||||
updated_at: { type: 'date' },
|
||||
updated_by: { type: 'keyword' },
|
||||
|
|
|
@ -238,6 +238,7 @@ Object {
|
|||
"policy_ids": Array [
|
||||
"policy123",
|
||||
],
|
||||
"supports_agentless": undefined,
|
||||
"vars": undefined,
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -49,6 +49,7 @@ export interface SimplifiedPackagePolicy {
|
|||
description?: string;
|
||||
vars?: SimplifiedVars;
|
||||
inputs?: SimplifiedInputs;
|
||||
supports_agentless?: boolean | null;
|
||||
}
|
||||
|
||||
export interface FormattedPackagePolicy extends Omit<PackagePolicy, 'inputs' | 'vars'> {
|
||||
|
@ -154,18 +155,19 @@ export function simplifiedPackagePolicytoNewPackagePolicy(
|
|||
description,
|
||||
inputs = {},
|
||||
vars: packageLevelVars,
|
||||
supports_agentless: supportsAgentless,
|
||||
} = data;
|
||||
const packagePolicy = packageToPackagePolicy(
|
||||
packageInfo,
|
||||
policyId && isEmpty(policyIds) ? policyId : policyIds,
|
||||
namespace,
|
||||
name,
|
||||
description
|
||||
);
|
||||
|
||||
if (outputId) {
|
||||
packagePolicy.output_id = outputId;
|
||||
}
|
||||
const packagePolicy = {
|
||||
...packageToPackagePolicy(
|
||||
packageInfo,
|
||||
policyId && isEmpty(policyIds) ? policyId : policyIds,
|
||||
namespace,
|
||||
name,
|
||||
description
|
||||
),
|
||||
supports_agentless: supportsAgentless,
|
||||
output_id: outputId,
|
||||
};
|
||||
|
||||
if (packagePolicy.package && options?.experimental_data_stream_features) {
|
||||
packagePolicy.package.experimental_data_stream_features =
|
||||
|
|
|
@ -93,6 +93,7 @@ export interface NewPackagePolicy {
|
|||
[key: string]: any;
|
||||
};
|
||||
overrides?: { inputs?: { [key: string]: any } } | null;
|
||||
supports_agentless?: boolean | null;
|
||||
}
|
||||
|
||||
export interface UpdatePackagePolicy extends NewPackagePolicy {
|
||||
|
|
|
@ -220,6 +220,8 @@ export interface GetAgentStatusResponse {
|
|||
export interface GetAgentIncomingDataRequest {
|
||||
query: {
|
||||
agentsIds: string[];
|
||||
pkgName?: string;
|
||||
pkgVersion?: string;
|
||||
previewData?: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -131,11 +131,11 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent<Props> = ({
|
|||
setAgentDataConfirmed,
|
||||
troubleshootLink,
|
||||
}) => {
|
||||
const { incomingData, dataPreview, isLoading, hasReachedTimeout } = usePollingIncomingData(
|
||||
const { incomingData, dataPreview, isLoading, hasReachedTimeout } = usePollingIncomingData({
|
||||
agentIds,
|
||||
true,
|
||||
MAX_AGENT_DATA_PREVIEW_COUNT
|
||||
);
|
||||
previewData: true,
|
||||
stopPollingAfterPreviewLength: MAX_AGENT_DATA_PREVIEW_COUNT,
|
||||
});
|
||||
const { enrolledAgents, numAgentsWithData } = useGetAgentIncomingData(incomingData, packageInfo);
|
||||
|
||||
const isGuidedOnboardingActive = useIsGuidedOnboardingActive(packageInfo?.name);
|
||||
|
|
|
@ -115,6 +115,7 @@ describe('useAgentless', () => {
|
|||
describe('useSetupTechnology', () => {
|
||||
const setNewAgentPolicy = jest.fn();
|
||||
const updateAgentPoliciesMock = jest.fn();
|
||||
const updatePackagePolicyMock = jest.fn();
|
||||
const setSelectedPolicyTabMock = jest.fn();
|
||||
const newAgentPolicyMock = {
|
||||
name: 'mock_new_agent_policy',
|
||||
|
@ -183,6 +184,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -212,6 +214,7 @@ describe('useSetupTechnology', () => {
|
|||
packagePolicy: packagePolicyMock,
|
||||
isEditPage: true,
|
||||
agentPolicies: [{ id: 'agentless-policy-id', supports_agentless: true } as any],
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -239,6 +242,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -248,6 +252,7 @@ describe('useSetupTechnology', () => {
|
|||
result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true });
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS);
|
||||
expect(setNewAgentPolicy).toHaveBeenCalledWith({
|
||||
name: 'Agentless policy for endpoint-1',
|
||||
|
@ -278,6 +283,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
};
|
||||
|
||||
const { result, rerender } = renderHook((props = initialProps) => useSetupTechnology(props), {
|
||||
|
@ -291,6 +297,7 @@ describe('useSetupTechnology', () => {
|
|||
});
|
||||
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS);
|
||||
expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true });
|
||||
expect(setNewAgentPolicy).toHaveBeenCalledWith({
|
||||
inactivity_timeout: 3600,
|
||||
name: 'Agentless policy for endpoint-1',
|
||||
|
@ -306,9 +313,11 @@ describe('useSetupTechnology', () => {
|
|||
...packagePolicyMock,
|
||||
name: 'endpoint-2',
|
||||
},
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS);
|
||||
expect(setNewAgentPolicy).toHaveBeenCalledWith({
|
||||
name: 'Agentless policy for endpoint-2',
|
||||
inactivity_timeout: 3600,
|
||||
|
@ -332,6 +341,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -366,6 +376,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -376,12 +387,14 @@ describe('useSetupTechnology', () => {
|
|||
});
|
||||
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS);
|
||||
expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true });
|
||||
|
||||
act(() => {
|
||||
result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED);
|
||||
});
|
||||
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED);
|
||||
expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: false });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock);
|
||||
|
@ -397,6 +410,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -410,6 +424,7 @@ describe('useSetupTechnology', () => {
|
|||
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED);
|
||||
|
||||
expect(updatePackagePolicyMock).not.toHaveBeenCalled();
|
||||
expect(setNewAgentPolicy).not.toHaveBeenCalled();
|
||||
expect(setSelectedPolicyTabMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -435,6 +450,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -454,6 +470,7 @@ describe('useSetupTechnology', () => {
|
|||
supports_agentless: true,
|
||||
inactivity_timeout: 3600,
|
||||
});
|
||||
expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true });
|
||||
});
|
||||
|
||||
act(() => {
|
||||
|
@ -463,6 +480,7 @@ describe('useSetupTechnology', () => {
|
|||
await waitFor(() => {
|
||||
expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED);
|
||||
expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock);
|
||||
expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: false });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -489,6 +507,7 @@ describe('useSetupTechnology', () => {
|
|||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
packageInfo: packageInfoMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -533,6 +552,7 @@ describe('useSetupTechnology', () => {
|
|||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
packageInfo: packageInfoMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -582,6 +602,7 @@ describe('useSetupTechnology', () => {
|
|||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
packageInfo: packageInfoMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -627,6 +648,7 @@ describe('useSetupTechnology', () => {
|
|||
updateAgentPolicies: updateAgentPoliciesMock,
|
||||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -673,6 +695,7 @@ describe('useSetupTechnology', () => {
|
|||
setSelectedPolicyTab: setSelectedPolicyTabMock,
|
||||
packagePolicy: packagePolicyMock,
|
||||
packageInfo: packageInfoMock,
|
||||
updatePackagePolicy: updatePackagePolicyMock,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ export function useSetupTechnology({
|
|||
setNewAgentPolicy,
|
||||
newAgentPolicy,
|
||||
updateAgentPolicies,
|
||||
updatePackagePolicy,
|
||||
setSelectedPolicyTab,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
|
@ -70,6 +71,7 @@ export function useSetupTechnology({
|
|||
setNewAgentPolicy: (policy: NewAgentPolicy) => void;
|
||||
newAgentPolicy: NewAgentPolicy;
|
||||
updateAgentPolicies: (policies: AgentPolicy[]) => void;
|
||||
updatePackagePolicy: (policy: Partial<NewPackagePolicy>) => void;
|
||||
setSelectedPolicyTab: (tab: SelectedPolicyTab) => void;
|
||||
packageInfo?: PackageInfo;
|
||||
packagePolicy: NewPackagePolicy;
|
||||
|
@ -112,16 +114,33 @@ export function useSetupTechnology({
|
|||
updateAgentPolicies([nextNewAgentlessPolicy] as AgentPolicy[]);
|
||||
}
|
||||
}
|
||||
if (
|
||||
selectedSetupTechnology === SetupTechnology.AGENTLESS &&
|
||||
!packagePolicy.supports_agentless
|
||||
) {
|
||||
updatePackagePolicy({
|
||||
supports_agentless: true,
|
||||
});
|
||||
} else if (
|
||||
selectedSetupTechnology !== SetupTechnology.AGENTLESS &&
|
||||
packagePolicy.supports_agentless
|
||||
) {
|
||||
updatePackagePolicy({
|
||||
supports_agentless: false,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
isAgentlessEnabled,
|
||||
isEditPage,
|
||||
newAgentlessPolicy,
|
||||
packagePolicy.name,
|
||||
packagePolicy.supports_agentless,
|
||||
selectedSetupTechnology,
|
||||
updateAgentPolicies,
|
||||
setNewAgentPolicy,
|
||||
agentPolicies,
|
||||
setSelectedSetupTechnology,
|
||||
updatePackagePolicy,
|
||||
]);
|
||||
|
||||
const handleSetupTechnologyChange = useCallback(
|
||||
|
@ -142,11 +161,17 @@ export function useSetupTechnology({
|
|||
setSelectedPolicyTab(SelectedPolicyTab.NEW);
|
||||
updateAgentPolicies([agentlessPolicy] as AgentPolicy[]);
|
||||
}
|
||||
updatePackagePolicy({
|
||||
supports_agentless: true,
|
||||
});
|
||||
} else if (setupTechnology === SetupTechnology.AGENT_BASED) {
|
||||
setNewAgentPolicy({
|
||||
...newAgentBasedPolicy.current,
|
||||
supports_agentless: false,
|
||||
});
|
||||
updatePackagePolicy({
|
||||
supports_agentless: false,
|
||||
});
|
||||
setSelectedPolicyTab(SelectedPolicyTab.NEW);
|
||||
updateAgentPolicies([newAgentBasedPolicy.current] as AgentPolicy[]);
|
||||
}
|
||||
|
@ -155,11 +180,12 @@ export function useSetupTechnology({
|
|||
[
|
||||
isAgentlessEnabled,
|
||||
selectedSetupTechnology,
|
||||
updatePackagePolicy,
|
||||
setNewAgentPolicy,
|
||||
newAgentlessPolicy,
|
||||
packageInfo,
|
||||
setSelectedPolicyTab,
|
||||
updateAgentPolicies,
|
||||
packageInfo,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -355,6 +355,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
|
|||
newAgentPolicy,
|
||||
setNewAgentPolicy,
|
||||
updateAgentPolicies,
|
||||
updatePackagePolicy,
|
||||
setSelectedPolicyTab,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
|
|
|
@ -134,6 +134,7 @@ export function usePackagePolicySteps({
|
|||
newAgentPolicy,
|
||||
setNewAgentPolicy,
|
||||
updateAgentPolicies,
|
||||
updatePackagePolicy,
|
||||
setSelectedPolicyTab,
|
||||
packagePolicy,
|
||||
isEditPage: true,
|
||||
|
|
|
@ -97,114 +97,122 @@ export const AgentDetailsIntegration: React.FunctionComponent<{
|
|||
agent: Agent;
|
||||
agentPolicy: AgentPolicy;
|
||||
packagePolicy: PackagePolicy;
|
||||
linkToLogs: boolean;
|
||||
'data-test-subj'?: string;
|
||||
}> = memo(({ agent, agentPolicy, packagePolicy, 'data-test-subj': dataTestSubj }) => {
|
||||
const { getHref } = useLink();
|
||||
const theme = useEuiTheme();
|
||||
}> = memo(
|
||||
({ agent, agentPolicy, packagePolicy, linkToLogs = true, 'data-test-subj': dataTestSubj }) => {
|
||||
const { getHref } = useLink();
|
||||
const theme = useEuiTheme();
|
||||
|
||||
const [isAttentionBadgeNeededForPolicyResponse, setIsAttentionBadgeNeededForPolicyResponse] =
|
||||
useState(false);
|
||||
const [isAttentionBadgeNeededForPolicyResponse, setIsAttentionBadgeNeededForPolicyResponse] =
|
||||
useState(false);
|
||||
|
||||
const policyResponseExtensionView = useUIExtension(
|
||||
packagePolicy.package?.name ?? '',
|
||||
'package-policy-response'
|
||||
);
|
||||
|
||||
const policyResponseExtensionViewWrapper = useMemo(() => {
|
||||
return (
|
||||
policyResponseExtensionView && (
|
||||
<ExtensionWrapper>
|
||||
<policyResponseExtensionView.Component
|
||||
agent={agent}
|
||||
onShowNeedsAttentionBadge={setIsAttentionBadgeNeededForPolicyResponse}
|
||||
/>
|
||||
</ExtensionWrapper>
|
||||
)
|
||||
const policyResponseExtensionView = useUIExtension(
|
||||
packagePolicy.package?.name ?? '',
|
||||
'package-policy-response'
|
||||
);
|
||||
}, [agent, policyResponseExtensionView]);
|
||||
|
||||
const packageErrors = useMemo(() => {
|
||||
if (!agent.components) {
|
||||
return [];
|
||||
}
|
||||
return getInputUnitsByPackage(agent.components, packagePolicy).filter(
|
||||
(u) => u.status === 'DEGRADED' || u.status === 'FAILED'
|
||||
);
|
||||
}, [agent.components, packagePolicy]);
|
||||
const policyResponseExtensionViewWrapper = useMemo(() => {
|
||||
return (
|
||||
policyResponseExtensionView && (
|
||||
<ExtensionWrapper>
|
||||
<policyResponseExtensionView.Component
|
||||
agent={agent}
|
||||
onShowNeedsAttentionBadge={setIsAttentionBadgeNeededForPolicyResponse}
|
||||
/>
|
||||
</ExtensionWrapper>
|
||||
)
|
||||
);
|
||||
}, [agent, policyResponseExtensionView]);
|
||||
|
||||
const showNeedsAttentionBadge = isAttentionBadgeNeededForPolicyResponse || !!packageErrors.length;
|
||||
|
||||
const genericErrorsListExtensionView = useUIExtension(
|
||||
packagePolicy.package?.name ?? '',
|
||||
'package-generic-errors-list'
|
||||
);
|
||||
|
||||
const genericErrorsListExtensionViewWrapper = useMemo(() => {
|
||||
return (
|
||||
genericErrorsListExtensionView && (
|
||||
<ExtensionWrapper>
|
||||
<genericErrorsListExtensionView.Component packageErrors={packageErrors} />
|
||||
</ExtensionWrapper>
|
||||
)
|
||||
);
|
||||
}, [packageErrors, genericErrorsListExtensionView]);
|
||||
|
||||
return (
|
||||
<CollapsablePanel
|
||||
id={packagePolicy.id}
|
||||
data-test-subj={dataTestSubj}
|
||||
title={
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{packagePolicy.package ? (
|
||||
<PackageIcon
|
||||
packageName={packagePolicy.package.name}
|
||||
version={packagePolicy.package.version}
|
||||
size="l"
|
||||
tryApi={true}
|
||||
/>
|
||||
) : (
|
||||
<PackageIcon size="l" packageName="default" version="0" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="eui-textTruncate">
|
||||
<EuiLink
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="agentPolicyDetailsLink"
|
||||
href={getHref('edit_integration', {
|
||||
policyId: agentPolicy.id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}
|
||||
>
|
||||
{packagePolicy.name}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
{showNeedsAttentionBadge && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
color={theme.euiTheme.colors.danger}
|
||||
iconType="warning"
|
||||
iconSide="left"
|
||||
data-test-subj={dataTestSubj ? `${dataTestSubj}-needsAttention` : undefined}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetailsIntegrations.needsAttention.label"
|
||||
defaultMessage="Needs attention"
|
||||
/>
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
const packageErrors = useMemo(() => {
|
||||
if (!agent.components) {
|
||||
return [];
|
||||
}
|
||||
>
|
||||
<AgentDetailsIntegrationInputs agent={agent} packagePolicy={packagePolicy} />
|
||||
{policyResponseExtensionViewWrapper}
|
||||
{genericErrorsListExtensionViewWrapper}
|
||||
<EuiSpacer />
|
||||
</CollapsablePanel>
|
||||
);
|
||||
});
|
||||
return getInputUnitsByPackage(agent.components, packagePolicy).filter(
|
||||
(u) => u.status === 'DEGRADED' || u.status === 'FAILED'
|
||||
);
|
||||
}, [agent.components, packagePolicy]);
|
||||
|
||||
const showNeedsAttentionBadge =
|
||||
isAttentionBadgeNeededForPolicyResponse || !!packageErrors.length;
|
||||
|
||||
const genericErrorsListExtensionView = useUIExtension(
|
||||
packagePolicy.package?.name ?? '',
|
||||
'package-generic-errors-list'
|
||||
);
|
||||
|
||||
const genericErrorsListExtensionViewWrapper = useMemo(() => {
|
||||
return (
|
||||
genericErrorsListExtensionView && (
|
||||
<ExtensionWrapper>
|
||||
<genericErrorsListExtensionView.Component packageErrors={packageErrors} />
|
||||
</ExtensionWrapper>
|
||||
)
|
||||
);
|
||||
}, [packageErrors, genericErrorsListExtensionView]);
|
||||
|
||||
return (
|
||||
<CollapsablePanel
|
||||
id={packagePolicy.id}
|
||||
data-test-subj={dataTestSubj}
|
||||
title={
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{packagePolicy.package ? (
|
||||
<PackageIcon
|
||||
packageName={packagePolicy.package.name}
|
||||
version={packagePolicy.package.version}
|
||||
size="l"
|
||||
tryApi={true}
|
||||
/>
|
||||
) : (
|
||||
<PackageIcon size="l" packageName="default" version="0" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="eui-textTruncate">
|
||||
<EuiLink
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="agentPolicyDetailsLink"
|
||||
href={getHref('edit_integration', {
|
||||
policyId: agentPolicy.id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}
|
||||
>
|
||||
{packagePolicy.name}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
{showNeedsAttentionBadge && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
color={theme.euiTheme.colors.danger}
|
||||
iconType="warning"
|
||||
iconSide="left"
|
||||
data-test-subj={dataTestSubj ? `${dataTestSubj}-needsAttention` : undefined}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentDetailsIntegrations.needsAttention.label"
|
||||
defaultMessage="Needs attention"
|
||||
/>
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
>
|
||||
<AgentDetailsIntegrationInputs
|
||||
agent={agent}
|
||||
packagePolicy={packagePolicy}
|
||||
linkToLogs={linkToLogs}
|
||||
/>
|
||||
{policyResponseExtensionViewWrapper}
|
||||
{genericErrorsListExtensionViewWrapper}
|
||||
<EuiSpacer size="s" />
|
||||
</CollapsablePanel>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -66,8 +66,9 @@ const StyledEuiTreeView = styled(EuiTreeView)`
|
|||
export const AgentDetailsIntegrationInputs: React.FunctionComponent<{
|
||||
agent: Agent;
|
||||
packagePolicy: PackagePolicy;
|
||||
linkToLogs?: boolean;
|
||||
'data-test-subj'?: string;
|
||||
}> = memo(({ agent, packagePolicy, 'data-test-subj': dataTestSubj }) => {
|
||||
}> = memo(({ agent, packagePolicy, linkToLogs = true, 'data-test-subj': dataTestSubj }) => {
|
||||
const { getHref } = useLink();
|
||||
|
||||
const inputStatusMap = useMemo(
|
||||
|
@ -138,21 +139,25 @@ export const AgentDetailsIntegrationInputs: React.FunctionComponent<{
|
|||
defaultMessage: 'View logs',
|
||||
})}
|
||||
>
|
||||
<StyledEuiLink
|
||||
href={getHref('agent_details', {
|
||||
agentId: agent.id,
|
||||
tabId: 'logs',
|
||||
logQuery: getLogsQueryByInputType(current.type),
|
||||
})}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.fleet.agentDetailsIntegrations.viewLogsButton',
|
||||
{
|
||||
defaultMessage: 'View logs',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{displayInputType(current.type)}
|
||||
</StyledEuiLink>
|
||||
{linkToLogs ? (
|
||||
<StyledEuiLink
|
||||
href={getHref('agent_details', {
|
||||
agentId: agent.id,
|
||||
tabId: 'logs',
|
||||
logQuery: getLogsQueryByInputType(current.type),
|
||||
})}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.fleet.agentDetailsIntegrations.viewLogsButton',
|
||||
{
|
||||
defaultMessage: 'View logs',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{displayInputType(current.type)}
|
||||
</StyledEuiLink>
|
||||
) : (
|
||||
<>{displayInputType(current.type)}</>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
),
|
||||
id: current.type,
|
||||
|
|
|
@ -15,7 +15,8 @@ import { AgentDetailsIntegration } from './agent_details_integration';
|
|||
export const AgentDetailsIntegrations: React.FunctionComponent<{
|
||||
agent: Agent;
|
||||
agentPolicy?: AgentPolicy;
|
||||
}> = memo(({ agent, agentPolicy }) => {
|
||||
linkToLogs?: boolean;
|
||||
}> = memo(({ agent, agentPolicy, linkToLogs = true }) => {
|
||||
if (!agentPolicy || !agentPolicy.package_policies) {
|
||||
return null;
|
||||
}
|
||||
|
@ -31,6 +32,7 @@ export const AgentDetailsIntegrations: React.FunctionComponent<{
|
|||
agent={agent}
|
||||
agentPolicy={agentPolicy}
|
||||
packagePolicy={packagePolicy}
|
||||
linkToLogs={linkToLogs}
|
||||
data-test-subj={`${testSubj}-accordion`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';
|
||||
import type { EuiBadgeProps } from '@elastic/eui';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiButton,
|
||||
|
@ -34,67 +35,75 @@ import { useAgentRefresh } from '../agent_details_page/hooks';
|
|||
|
||||
import { AgentUpgradeAgentModal } from './agent_upgrade_modal';
|
||||
|
||||
interface Props {
|
||||
type Props = EuiBadgeProps & {
|
||||
agent: Agent;
|
||||
fromDetails?: boolean;
|
||||
}
|
||||
|
||||
const Status = {
|
||||
Healthy: (
|
||||
<EuiBadge color="success">
|
||||
<FormattedMessage id="xpack.fleet.agentHealth.healthyStatusText" defaultMessage="Healthy" />
|
||||
</EuiBadge>
|
||||
),
|
||||
Offline: (
|
||||
<EuiBadge color="default">
|
||||
<FormattedMessage id="xpack.fleet.agentHealth.offlineStatusText" defaultMessage="Offline" />
|
||||
</EuiBadge>
|
||||
),
|
||||
Inactive: (
|
||||
<EuiBadge color={euiVars.euiColorDarkShade}>
|
||||
<FormattedMessage id="xpack.fleet.agentHealth.inactiveStatusText" defaultMessage="Inactive" />
|
||||
</EuiBadge>
|
||||
),
|
||||
Unenrolled: (
|
||||
<EuiBadge color={euiVars.euiColorDisabled}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.unenrolledStatusText"
|
||||
defaultMessage="Unenrolled"
|
||||
/>
|
||||
</EuiBadge>
|
||||
),
|
||||
Unhealthy: (
|
||||
<EuiBadge color="warning">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.unhealthyStatusText"
|
||||
defaultMessage="Unhealthy"
|
||||
/>
|
||||
</EuiBadge>
|
||||
),
|
||||
Updating: (
|
||||
<EuiBadge color="primary">
|
||||
<FormattedMessage id="xpack.fleet.agentHealth.updatingStatusText" defaultMessage="Updating" />
|
||||
</EuiBadge>
|
||||
),
|
||||
};
|
||||
|
||||
function getStatusComponent(status: Agent['status']): React.ReactElement {
|
||||
function getStatusComponent({
|
||||
status,
|
||||
...restOfProps
|
||||
}: {
|
||||
status: Agent['status'];
|
||||
} & EuiBadgeProps): React.ReactElement {
|
||||
switch (status) {
|
||||
case 'error':
|
||||
case 'degraded':
|
||||
return Status.Unhealthy;
|
||||
return (
|
||||
<EuiBadge color="warning" {...restOfProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.unhealthyStatusText"
|
||||
defaultMessage="Unhealthy"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
case 'inactive':
|
||||
return Status.Inactive;
|
||||
return (
|
||||
<EuiBadge color={euiVars.euiColorDarkShade} {...restOfProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.inactiveStatusText"
|
||||
defaultMessage="Inactive"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
case 'offline':
|
||||
return Status.Offline;
|
||||
return (
|
||||
<EuiBadge color="default" {...restOfProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.offlineStatusText"
|
||||
defaultMessage="Offline"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
case 'unenrolling':
|
||||
case 'enrolling':
|
||||
case 'updating':
|
||||
return Status.Updating;
|
||||
return (
|
||||
<EuiBadge color="primary" {...restOfProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.updatingStatusText"
|
||||
defaultMessage="Updating"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
case 'unenrolled':
|
||||
return Status.Unenrolled;
|
||||
return (
|
||||
<EuiBadge color={euiVars.euiColorDisabled} {...restOfProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.unenrolledStatusText"
|
||||
defaultMessage="Unenrolled"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
default:
|
||||
return Status.Healthy;
|
||||
return (
|
||||
<EuiBadge color="success" {...restOfProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentHealth.healthyStatusText"
|
||||
defaultMessage="Healthy"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +111,11 @@ const WrappedEuiCallOut = styled(EuiCallOut)`
|
|||
white-space: wrap !important;
|
||||
`;
|
||||
|
||||
export const AgentHealth: React.FunctionComponent<Props> = ({ agent, fromDetails }) => {
|
||||
export const AgentHealth: React.FunctionComponent<Props> = ({
|
||||
agent,
|
||||
fromDetails,
|
||||
...restOfProps
|
||||
}) => {
|
||||
const { last_checkin: lastCheckIn, last_checkin_message: lastCheckInMessage } = agent;
|
||||
const msLastCheckIn = new Date(lastCheckIn || 0).getTime();
|
||||
const lastCheckInMessageText = lastCheckInMessage ? (
|
||||
|
@ -172,14 +185,16 @@ export const AgentHealth: React.FunctionComponent<Props> = ({ agent, fromDetails
|
|||
>
|
||||
{isStuckInUpdating(agent) && !fromDetails ? (
|
||||
<div className="eui-textNoWrap">
|
||||
{getStatusComponent(agent.status)}
|
||||
{getStatusComponent({ status: agent.status, ...restOfProps })}
|
||||
|
||||
<EuiIcon type="warning" color="warning" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{getStatusComponent(agent.status)}
|
||||
{previousToOfflineStatus ? getStatusComponent(previousToOfflineStatus) : null}
|
||||
{getStatusComponent({ status: agent.status, ...restOfProps })}
|
||||
{previousToOfflineStatus
|
||||
? getStatusComponent({ status: previousToOfflineStatus, ...restOfProps })
|
||||
: null}
|
||||
</>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
|
|
|
@ -846,7 +846,7 @@ export function Detail() {
|
|||
</Route>
|
||||
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_policies}>
|
||||
{canReadIntegrationPolicies ? (
|
||||
<PackagePoliciesPage name={packageInfo.name} version={packageInfo.version} />
|
||||
<PackagePoliciesPage packageInfo={packageInfo} />
|
||||
) : (
|
||||
<PermissionsError
|
||||
error="MISSING_PRIVILEGES"
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 { screen, fireEvent, act } from '@testing-library/react';
|
||||
|
||||
import { createIntegrationsTestRendererMock } from '../../../../../../../../mock';
|
||||
|
||||
import { AgentBasedPackagePoliciesTable } from './agent_based_table';
|
||||
|
||||
const mockPackagePolicies = [
|
||||
{
|
||||
agentPolicies: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Agent Policy 1',
|
||||
status: 'active' as const,
|
||||
is_managed: false,
|
||||
is_protected: false,
|
||||
updated_at: '2023-01-01T00:00:00Z',
|
||||
updated_by: 'user',
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
created_by: 'user',
|
||||
namespace: 'default',
|
||||
revision: 1,
|
||||
monitoring_enabled: [],
|
||||
},
|
||||
],
|
||||
packagePolicy: {
|
||||
id: 'pkg1',
|
||||
name: 'Package Policy 1',
|
||||
package: { name: 'package-name', title: 'Package Title', version: '1.0.0' },
|
||||
hasUpgrade: true,
|
||||
inputs: [],
|
||||
revision: 1,
|
||||
updated_at: '2023-01-01T00:00:00Z',
|
||||
updated_by: 'user',
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
created_by: 'user',
|
||||
namespace: 'default',
|
||||
policy_id: 'policy1',
|
||||
policy_ids: ['policy1'],
|
||||
enabled: true,
|
||||
},
|
||||
rowIndex: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const mockPagination = {
|
||||
pagination: { currentPage: 1, pageSize: 10, totalItemCount: 1, pageSizeOptions: [10, 20, 50] },
|
||||
setPagination: jest.fn(),
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
};
|
||||
|
||||
describe('AgentBasedPackagePoliciesTable', () => {
|
||||
it('renders the table with package policies', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(
|
||||
<AgentBasedPackagePoliciesTable
|
||||
isLoading={false}
|
||||
packagePolicies={mockPackagePolicies}
|
||||
packagePoliciesTotal={1}
|
||||
refreshPackagePolicies={jest.fn()}
|
||||
pagination={mockPagination}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
expect(result.getByText('Integration policy')).toBeInTheDocument();
|
||||
expect(result.getByText('Package Policy 1')).toBeInTheDocument();
|
||||
expect(result.getByText('v1.0.0')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows loading message when isLoading is true', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(
|
||||
<AgentBasedPackagePoliciesTable
|
||||
isLoading={true}
|
||||
packagePolicies={[]}
|
||||
packagePoliciesTotal={0}
|
||||
refreshPackagePolicies={jest.fn()}
|
||||
pagination={mockPagination}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
expect(result.getByText('Loading integration policies…')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows no policies message when there are no package policies', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(
|
||||
<AgentBasedPackagePoliciesTable
|
||||
isLoading={false}
|
||||
packagePolicies={[]}
|
||||
packagePoliciesTotal={0}
|
||||
refreshPackagePolicies={jest.fn()}
|
||||
pagination={mockPagination}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
expect(result.getByText('No integration policies')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('opens the agent enrollment flyout when add agent button is clicked', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(
|
||||
<AgentBasedPackagePoliciesTable
|
||||
isLoading={false}
|
||||
packagePolicies={mockPackagePolicies}
|
||||
packagePoliciesTotal={1}
|
||||
refreshPackagePolicies={jest.fn()}
|
||||
pagination={mockPagination}
|
||||
/>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('Add agent'));
|
||||
});
|
||||
expect(result.getByTestId('agentEnrollmentFlyout')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* 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 { stringify, parse } from 'query-string';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useLocation, useHistory } from 'react-router-dom';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiLink,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { AgentPolicy, InMemoryPackagePolicy, PackagePolicy } from '../../../../../../types';
|
||||
import type { usePagination } from '../../../../../../hooks';
|
||||
import { useLink, useAuthz, useMultipleAgentPolicies } from '../../../../../../hooks';
|
||||
import {
|
||||
AgentEnrollmentFlyout,
|
||||
MultipleAgentPoliciesSummaryLine,
|
||||
AgentPolicySummaryLine,
|
||||
PackagePolicyActionsMenu,
|
||||
} from '../../../../../../components';
|
||||
|
||||
import { Persona } from '../persona';
|
||||
|
||||
import { PackagePolicyAgentsCell } from './package_policy_agents_cell';
|
||||
|
||||
export const AgentBasedPackagePoliciesTable = ({
|
||||
isLoading,
|
||||
packagePolicies,
|
||||
packagePoliciesTotal,
|
||||
refreshPackagePolicies,
|
||||
pagination,
|
||||
addAgentToPolicyIdFromParams,
|
||||
showAddAgentHelpForPolicyId,
|
||||
}: {
|
||||
isLoading: boolean;
|
||||
packagePolicies: Array<{
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}>;
|
||||
packagePoliciesTotal: number;
|
||||
refreshPackagePolicies: () => void;
|
||||
pagination: ReturnType<typeof usePagination>;
|
||||
addAgentToPolicyIdFromParams?: string | null;
|
||||
showAddAgentHelpForPolicyId?: string | null;
|
||||
}) => {
|
||||
const { getHref } = useLink();
|
||||
const { search } = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const [selectedTableIndex, setSelectedTableIndex] = useState<number | undefined>();
|
||||
const { canUseMultipleAgentPolicies } = useMultipleAgentPolicies();
|
||||
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||
const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies;
|
||||
const canReadAgentPolicies = useAuthz().fleet.readAgentPolicies;
|
||||
const canShowMultiplePoliciesCell =
|
||||
canUseMultipleAgentPolicies && canReadIntegrationPolicies && canReadAgentPolicies;
|
||||
|
||||
// Show tour help for adding agents to a policy
|
||||
const addAgentHelpForPolicyId = packagePolicies.find(({ agentPolicies }) =>
|
||||
agentPolicies.find((agentPolicy) => agentPolicy.id === showAddAgentHelpForPolicyId)
|
||||
)?.packagePolicy?.id;
|
||||
|
||||
// Handle the "add agent" link displayed in post-installation toast notifications in the case
|
||||
// where a user is clicking the link while on the package policies listing page
|
||||
const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string | null>(
|
||||
addAgentToPolicyIdFromParams || null
|
||||
);
|
||||
useEffect(() => {
|
||||
const unlisten = history.listen((location) => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const addAgentToPolicyId = params.get('addAgentToPolicyId');
|
||||
|
||||
if (addAgentToPolicyId) {
|
||||
setFlyoutOpenForPolicyId(addAgentToPolicyId);
|
||||
}
|
||||
});
|
||||
|
||||
return () => unlisten();
|
||||
}, [history]);
|
||||
|
||||
const selectedPolicies =
|
||||
selectedTableIndex !== undefined ? packagePolicies[selectedTableIndex] : undefined;
|
||||
const selectedAgentPolicies = selectedPolicies?.agentPolicies;
|
||||
const selectedPackagePolicy = selectedPolicies?.packagePolicy;
|
||||
const flyoutPolicy = selectedAgentPolicies?.length === 1 ? selectedAgentPolicies[0] : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiBasicTable<{
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}>
|
||||
items={packagePolicies || []}
|
||||
columns={[
|
||||
{
|
||||
field: 'packagePolicy.name',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', {
|
||||
defaultMessage: 'Integration policy',
|
||||
}),
|
||||
render(_, { agentPolicies, packagePolicy }) {
|
||||
return (
|
||||
<EuiLink
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="integrationNameLink"
|
||||
href={getHref('integration_policy_edit', {
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}
|
||||
>
|
||||
{packagePolicy.name}
|
||||
</EuiLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.package.version',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.version', {
|
||||
defaultMessage: 'Version',
|
||||
}),
|
||||
render(_version, { agentPolicies, packagePolicy }) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" wrap={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText
|
||||
size="s"
|
||||
className="eui-textNoWrap"
|
||||
data-test-subj="packageVersionText"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.packageVersion"
|
||||
defaultMessage="v{version}"
|
||||
values={{ version: _version }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{agentPolicies.length > 0 && packagePolicy.hasUpgrade && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
minWidth="0"
|
||||
href={`${getHref('upgrade_package_policy', {
|
||||
policyId: agentPolicies[0].id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}?from=integrations-policy-list`}
|
||||
data-test-subj="integrationPolicyUpgradeBtn"
|
||||
isDisabled={!canWriteIntegrationPolicies}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton"
|
||||
defaultMessage="Upgrade"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.policy_ids',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentPolicy', {
|
||||
defaultMessage: 'Agent policies',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(ids, { agentPolicies, packagePolicy }) {
|
||||
return agentPolicies.length > 0 ? (
|
||||
canShowMultiplePoliciesCell ? (
|
||||
<MultipleAgentPoliciesSummaryLine
|
||||
policies={agentPolicies}
|
||||
packagePolicyId={packagePolicy.id}
|
||||
onAgentPoliciesChange={refreshPackagePolicies}
|
||||
/>
|
||||
) : (
|
||||
<AgentPolicySummaryLine policy={agentPolicies[0]} />
|
||||
)
|
||||
) : ids.length === 0 ? (
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.noAgentPolicies"
|
||||
defaultMessage="No agent policies"
|
||||
/>
|
||||
</EuiText>
|
||||
) : (
|
||||
<EuiText color="subdued" size="xs">
|
||||
<EuiIcon size="m" type="warning" color="warning" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.agentPolicyDeletedWarning"
|
||||
defaultMessage="Policy not found"
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.updated_by',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedBy', {
|
||||
defaultMessage: 'Last updated by',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(updatedBy: PackagePolicy['updated_by']) {
|
||||
return <Persona size="s" name={updatedBy} title={updatedBy} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.updated_at',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedAt', {
|
||||
defaultMessage: 'Last updated',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(updatedAt: PackagePolicy['updated_at']) {
|
||||
return (
|
||||
<span className="eui-textTruncate" title={updatedAt}>
|
||||
<FormattedRelative value={updatedAt} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentCount', {
|
||||
defaultMessage: 'Agents',
|
||||
}),
|
||||
render({
|
||||
agentPolicies,
|
||||
packagePolicy,
|
||||
rowIndex,
|
||||
}: {
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}) {
|
||||
if (agentPolicies.length === 0) {
|
||||
return (
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.noAgents"
|
||||
defaultMessage="No agents"
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PackagePolicyAgentsCell
|
||||
agentPolicies={agentPolicies}
|
||||
onAddAgent={() => {
|
||||
setSelectedTableIndex(rowIndex);
|
||||
setFlyoutOpenForPolicyId(agentPolicies[0].id);
|
||||
}}
|
||||
hasHelpPopover={addAgentHelpForPolicyId === packagePolicy.id}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
width: '8ch',
|
||||
align: 'right',
|
||||
render({
|
||||
agentPolicies,
|
||||
packagePolicy,
|
||||
}: {
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
}) {
|
||||
const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies
|
||||
return (
|
||||
<PackagePolicyActionsMenu
|
||||
agentPolicies={agentPolicies}
|
||||
packagePolicy={packagePolicy}
|
||||
showAddAgent={true}
|
||||
upgradePackagePolicyHref={
|
||||
agentPolicy
|
||||
? `${getHref('upgrade_package_policy', {
|
||||
policyId: agentPolicy.id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}?from=integrations-policy-list`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
loading={isLoading}
|
||||
data-test-subj="integrationPolicyTable"
|
||||
pagination={{
|
||||
pageIndex: pagination.pagination.currentPage - 1,
|
||||
pageSize: pagination.pagination.pageSize,
|
||||
totalItemCount: packagePoliciesTotal,
|
||||
pageSizeOptions: pagination.pageSizeOptions,
|
||||
}}
|
||||
onChange={({ page }: { page: { index: number; size: number } }) => {
|
||||
pagination.setPagination({
|
||||
currentPage: page.index + 1,
|
||||
pageSize: page.size,
|
||||
});
|
||||
}}
|
||||
noItemsMessage={
|
||||
isLoading ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.loadingPoliciesMessage"
|
||||
defaultMessage="Loading integration policies…"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.noPoliciesMessage"
|
||||
defaultMessage="No integration policies"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{flyoutOpenForPolicyId && selectedAgentPolicies && !isLoading && (
|
||||
<AgentEnrollmentFlyout
|
||||
onClose={() => {
|
||||
setFlyoutOpenForPolicyId(null);
|
||||
const { addAgentToPolicyId, ...rest } = parse(search);
|
||||
history.replace({ search: stringify(rest) });
|
||||
}}
|
||||
agentPolicy={flyoutPolicy}
|
||||
selectedAgentPolicies={selectedAgentPolicies}
|
||||
isIntegrationFlow={true}
|
||||
installedPackagePolicy={{
|
||||
name: selectedPackagePolicy?.package?.name || '',
|
||||
version: selectedPackagePolicy?.package?.version || '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* 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 { fireEvent, act, waitFor } from '@testing-library/react';
|
||||
|
||||
import { AGENTS_PREFIX } from '../../../../../../../../../common/constants';
|
||||
import { sendGetAgents } from '../../../../../../hooks';
|
||||
import { createIntegrationsTestRendererMock } from '../../../../../../../../mock';
|
||||
|
||||
import { AgentlessPackagePoliciesTable } from './agentless_table';
|
||||
|
||||
jest.mock('../../../../../../hooks', () => ({
|
||||
...jest.requireActual('../../../../../../hooks'),
|
||||
useConfirmForceInstall: jest.fn(),
|
||||
sendGetAgents: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('AgentlessPackagePoliciesTable', () => {
|
||||
const mockSendGetAgents = sendGetAgents as jest.MockedFunction<typeof sendGetAgents>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSendGetAgents.mockResolvedValue({
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
policy_id: 'policy1',
|
||||
id: 'agent1',
|
||||
packages: ['package'],
|
||||
type: 'PERMANENT',
|
||||
active: true,
|
||||
enrolled_at: '2023-01-01T00:00:00Z',
|
||||
local_metadata: {},
|
||||
status: 'online',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
perPage: 10000,
|
||||
},
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
isLoading: false,
|
||||
packagePolicies: [
|
||||
{
|
||||
agentPolicies: [
|
||||
{
|
||||
id: 'policy1',
|
||||
name: 'Policy 1',
|
||||
status: 'active' as const,
|
||||
is_managed: false,
|
||||
updated_at: '2023-01-01T00:00:00Z',
|
||||
updated_by: 'user1',
|
||||
namespace: 'default',
|
||||
monitoring_enabled: [],
|
||||
revision: 1,
|
||||
is_protected: false,
|
||||
},
|
||||
],
|
||||
packagePolicy: {
|
||||
id: 'packagePolicy1',
|
||||
name: 'Package Policy 1',
|
||||
updated_by: 'user1',
|
||||
updated_at: '2023-01-01T00:00:00Z',
|
||||
inputs: [],
|
||||
policy_id: 'policy1',
|
||||
namespace: 'default',
|
||||
enabled: true,
|
||||
package: {
|
||||
name: 'package',
|
||||
title: 'Package',
|
||||
version: '1.0.0',
|
||||
},
|
||||
hasUpgrade: false,
|
||||
revision: 1,
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
created_by: 'user1',
|
||||
policy_ids: ['policy1'],
|
||||
},
|
||||
rowIndex: 0,
|
||||
},
|
||||
],
|
||||
packagePoliciesTotal: 1,
|
||||
refreshPackagePolicies: jest.fn(),
|
||||
pagination: {
|
||||
pagination: { currentPage: 1, pageSize: 10 },
|
||||
setPagination: jest.fn(),
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
},
|
||||
};
|
||||
|
||||
it('shows loading message when isLoading is true', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(
|
||||
<AgentlessPackagePoliciesTable {...defaultProps} packagePolicies={[]} isLoading={true} />
|
||||
);
|
||||
await act(async () => {
|
||||
expect(result.getByText('Loading integration policies…')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows no items message when there are no package policies', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(
|
||||
<AgentlessPackagePoliciesTable {...defaultProps} packagePolicies={[]} />
|
||||
);
|
||||
await act(async () => {
|
||||
expect(result.getByText('No agentless integration policies')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the table with package policies', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(<AgentlessPackagePoliciesTable {...defaultProps} />);
|
||||
|
||||
await act(async () => {
|
||||
expect(result.getByText('Package Policy 1')).toBeInTheDocument();
|
||||
expect(result.getByText('user1')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays agent health status when agents are loaded', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(<AgentlessPackagePoliciesTable {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(mockSendGetAgents).toHaveBeenCalledWith({
|
||||
perPage: 10000,
|
||||
kuery: `${AGENTS_PREFIX}.policy_id: "policy1"`,
|
||||
});
|
||||
});
|
||||
expect(await result.findByText('Healthy')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens flyout when status badge is clicked', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const result = renderer.render(<AgentlessPackagePoliciesTable {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(mockSendGetAgents).toHaveBeenCalledWith({
|
||||
perPage: 10000,
|
||||
kuery: `${AGENTS_PREFIX}.policy_id: "policy1"`,
|
||||
});
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.click(await result.findByText('Healthy'));
|
||||
});
|
||||
expect(result.getByText('Confirm agentless enrollment')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* 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, { useEffect, useMemo, useState } from 'react';
|
||||
import type { HorizontalAlignment } from '@elastic/eui';
|
||||
import { EuiBadge, EuiBasicTable, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type {
|
||||
Agent,
|
||||
AgentPolicy,
|
||||
InMemoryPackagePolicy,
|
||||
PackagePolicy,
|
||||
} from '../../../../../../types';
|
||||
import { AGENTS_PREFIX, SO_SEARCH_LIMIT } from '../../../../../../../../../common/constants';
|
||||
import type { usePagination } from '../../../../../../hooks';
|
||||
import { useLink, sendGetAgents, useAuthz, useStartServices } from '../../../../../../hooks';
|
||||
import {
|
||||
Loading,
|
||||
PackagePolicyActionsMenu,
|
||||
AgentlessEnrollmentFlyout,
|
||||
} from '../../../../../../components';
|
||||
|
||||
import { Persona } from '../persona';
|
||||
import { AgentHealth } from '../../../../../../../fleet/sections/agents/components';
|
||||
|
||||
const REFRESH_INTERVAL_MS = 30000;
|
||||
|
||||
export const AgentlessPackagePoliciesTable = ({
|
||||
isLoading,
|
||||
packagePolicies,
|
||||
packagePoliciesTotal,
|
||||
refreshPackagePolicies,
|
||||
pagination,
|
||||
}: {
|
||||
isLoading: boolean;
|
||||
packagePolicies: Array<{
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}>;
|
||||
packagePoliciesTotal: number;
|
||||
refreshPackagePolicies: () => void;
|
||||
pagination: ReturnType<typeof usePagination>;
|
||||
}) => {
|
||||
const core = useStartServices();
|
||||
const { notifications } = core;
|
||||
const authz = useAuthz();
|
||||
const { getHref } = useLink();
|
||||
const [isAgentsLoading, setIsAgentsLoading] = useState<boolean>(false);
|
||||
const [agentsByPolicyId, setAgentsByPolicyId] = useState<Record<string, Agent>>({});
|
||||
const canReadAgents = authz.fleet.readAgents;
|
||||
|
||||
// Kuery for all agents enrolled into the agent policies associated with the package policies
|
||||
// We use the first agent policy as agentless package policies have a 1:1 relationship with agent policies
|
||||
// Maximum # of agent policies is 50, based on the max page size in UI
|
||||
const agentsKuery = useMemo(() => {
|
||||
return packagePolicies
|
||||
.reduce((policyIds, { agentPolicies }) => {
|
||||
return [...policyIds, ...(agentPolicies[0] ? [agentPolicies[0]?.id] : [])];
|
||||
}, [] as string[])
|
||||
.map((policyId) => `${AGENTS_PREFIX}.policy_id: "${policyId}"`)
|
||||
.join(' or ');
|
||||
}, [packagePolicies]);
|
||||
|
||||
// Fetch agents using above kuery, if the user has access to read agents
|
||||
// Polls every 30 seconds
|
||||
useEffect(() => {
|
||||
const fetchAgents = async () => {
|
||||
const { data: agentsData, error } = await sendGetAgents({
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
kuery: agentsKuery,
|
||||
});
|
||||
|
||||
setAgentsByPolicyId(
|
||||
(agentsData?.items || []).reduce((acc, agent) => {
|
||||
if (agent.policy_id) {
|
||||
acc[agent.policy_id] = agent;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, Agent>)
|
||||
);
|
||||
|
||||
if (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate(
|
||||
'xpack.fleet.epm.packageDetails.integrationList.agentlessStatusError',
|
||||
{
|
||||
defaultMessage: 'Error fetching agentless status information',
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
setIsAgentsLoading(false);
|
||||
};
|
||||
|
||||
if (canReadAgents) {
|
||||
setIsAgentsLoading(true);
|
||||
fetchAgents();
|
||||
const interval = setInterval(() => {
|
||||
fetchAgents();
|
||||
}, REFRESH_INTERVAL_MS);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [agentsKuery, canReadAgents, notifications.toasts]);
|
||||
|
||||
// Flyout state
|
||||
const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string>();
|
||||
const [flyoutPackagePolicy, setFlyoutPackagePolicy] = useState<PackagePolicy>();
|
||||
const [flyoutAgentPolicy, setFlyoutAgentPolicy] = useState<AgentPolicy>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiBasicTable<{
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}>
|
||||
items={packagePolicies || []}
|
||||
columns={[
|
||||
{
|
||||
field: 'packagePolicy.name',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', {
|
||||
defaultMessage: 'Integration policy',
|
||||
}),
|
||||
render(_, { agentPolicies, packagePolicy }) {
|
||||
return (
|
||||
<EuiLink
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="agentlessIntegrationNameLink"
|
||||
href={getHref('integration_policy_edit', {
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}
|
||||
>
|
||||
{packagePolicy.name}
|
||||
</EuiLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.updated_by',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedBy', {
|
||||
defaultMessage: 'Last updated by',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(updatedBy: PackagePolicy['updated_by']) {
|
||||
return <Persona size="s" name={updatedBy} title={updatedBy} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.updated_at',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedAt', {
|
||||
defaultMessage: 'Last updated',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(updatedAt: PackagePolicy['updated_at']) {
|
||||
return (
|
||||
<span className="eui-textTruncate" title={updatedAt}>
|
||||
<FormattedRelative value={updatedAt} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
...(canReadAgents
|
||||
? [
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate(
|
||||
'xpack.fleet.epm.packageDetails.integrationList.agentlessStatus',
|
||||
{
|
||||
defaultMessage: 'Status',
|
||||
}
|
||||
),
|
||||
align: 'left' as HorizontalAlignment,
|
||||
render({
|
||||
agentPolicies,
|
||||
packagePolicy,
|
||||
}: {
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}) {
|
||||
if (isAgentsLoading) {
|
||||
return <Loading size="s" />;
|
||||
}
|
||||
// Use the first agent policy ID associated with the package policy
|
||||
// because agentless package policies are only associated with one agent policy
|
||||
const agentPolicy = agentPolicies[0];
|
||||
const agent =
|
||||
(agentPolicy?.id && agentsByPolicyId[agentPolicy.id]) || undefined;
|
||||
|
||||
// Status badge click handler
|
||||
const statusBadgeProps = {
|
||||
onClick: () => {
|
||||
setFlyoutOpenForPolicyId(packagePolicy.id);
|
||||
setFlyoutPackagePolicy(packagePolicy);
|
||||
setFlyoutAgentPolicy(agentPolicy);
|
||||
},
|
||||
'data-test-subj': 'agentlessStatusBadge',
|
||||
onClickAriaLabel: i18n.translate(
|
||||
'xpack.fleet.epm.packageDetails.integrationList.agentlessStatusAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Open status details',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
return agent ? (
|
||||
<AgentHealth agent={agent} {...statusBadgeProps} />
|
||||
) : (
|
||||
<EuiBadge color="default" {...statusBadgeProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.packageDetails.integrationList.pendingAgentlessStatus"
|
||||
defaultMessage="Pending"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
width: '8ch',
|
||||
align: 'right' as HorizontalAlignment,
|
||||
render({
|
||||
agentPolicies,
|
||||
packagePolicy,
|
||||
}: {
|
||||
agentPolicies: AgentPolicy[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
}) {
|
||||
const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies
|
||||
return (
|
||||
<PackagePolicyActionsMenu
|
||||
agentPolicies={agentPolicies}
|
||||
packagePolicy={packagePolicy}
|
||||
showAddAgent={true}
|
||||
upgradePackagePolicyHref={
|
||||
agentPolicy
|
||||
? `${getHref('upgrade_package_policy', {
|
||||
policyId: agentPolicy.id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}?from=integrations-policy-list`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
loading={isLoading}
|
||||
data-test-subj="integrationPolicyTable"
|
||||
pagination={{
|
||||
pageIndex: pagination.pagination.currentPage - 1,
|
||||
pageSize: pagination.pagination.pageSize,
|
||||
totalItemCount: packagePoliciesTotal,
|
||||
pageSizeOptions: pagination.pageSizeOptions,
|
||||
}}
|
||||
onChange={({ page }: { page: { index: number; size: number } }) => {
|
||||
pagination.setPagination({
|
||||
currentPage: page.index + 1,
|
||||
pageSize: page.size,
|
||||
});
|
||||
}}
|
||||
noItemsMessage={
|
||||
isLoading ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.loadingPoliciesMessage"
|
||||
defaultMessage="Loading integration policies…"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.noAgentlessPoliciesMessage"
|
||||
defaultMessage="No agentless integration policies"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{flyoutOpenForPolicyId && flyoutPackagePolicy && (
|
||||
<AgentlessEnrollmentFlyout
|
||||
onClose={() => {
|
||||
setFlyoutOpenForPolicyId(undefined);
|
||||
setFlyoutPackagePolicy(undefined);
|
||||
setFlyoutAgentPolicy(undefined);
|
||||
}}
|
||||
packagePolicy={flyoutPackagePolicy}
|
||||
agentPolicy={flyoutAgentPolicy}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -4,80 +4,49 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { stringify, parse } from 'query-string';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Redirect, useLocation, useHistory } from 'react-router-dom';
|
||||
import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Redirect, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiLink,
|
||||
EuiAccordion,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiNotificationBadge,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { InstallStatus } from '../../../../../types';
|
||||
import type { GetAgentPoliciesResponseItem, InMemoryPackagePolicy } from '../../../../../types';
|
||||
import type {
|
||||
AgentPolicy,
|
||||
GetAgentPoliciesResponseItem,
|
||||
InMemoryPackagePolicy,
|
||||
PackageInfo,
|
||||
PackagePolicy,
|
||||
} from '../../../../../types';
|
||||
import {
|
||||
useLink,
|
||||
useUrlPagination,
|
||||
useGetPackageInstallStatus,
|
||||
AgentPolicyRefreshContext,
|
||||
useIsPackagePolicyUpgradable,
|
||||
useAuthz,
|
||||
useMultipleAgentPolicies,
|
||||
usePagination,
|
||||
} from '../../../../../hooks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||
import {
|
||||
AgentEnrollmentFlyout,
|
||||
MultipleAgentPoliciesSummaryLine,
|
||||
AgentPolicySummaryLine,
|
||||
PackagePolicyActionsMenu,
|
||||
} from '../../../../../components';
|
||||
import { SideBarColumn } from '../../../components/side_bar_column';
|
||||
|
||||
import { PackagePolicyAgentsCell } from './components/package_policy_agents_cell';
|
||||
import { useAgentless } from '../../../../../../fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology';
|
||||
|
||||
import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_agent_policy';
|
||||
import { Persona } from './persona';
|
||||
import { AgentBasedPackagePoliciesTable } from './components/agent_based_table';
|
||||
import { AgentlessPackagePoliciesTable } from './components/agentless_table';
|
||||
|
||||
interface PackagePoliciesPanelProps {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface InMemoryPackagePolicyAndAgentPolicy {
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
agentPolicies: GetAgentPoliciesResponseItem[];
|
||||
rowIndex: number;
|
||||
}
|
||||
|
||||
const IntegrationDetailsLink = memo<{
|
||||
packagePolicy: InMemoryPackagePolicyAndAgentPolicy['packagePolicy'];
|
||||
agentPolicies: InMemoryPackagePolicyAndAgentPolicy['agentPolicies'];
|
||||
}>(({ packagePolicy }) => {
|
||||
const { getHref } = useLink();
|
||||
return (
|
||||
<EuiLink
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="integrationNameLink"
|
||||
href={getHref('integration_policy_edit', {
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}
|
||||
>
|
||||
{packagePolicy.name}
|
||||
</EuiLink>
|
||||
);
|
||||
});
|
||||
|
||||
export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps) => {
|
||||
export const PackagePoliciesPage = ({ packageInfo }: { packageInfo: PackageInfo }) => {
|
||||
const { name, version } = packageInfo;
|
||||
const { search } = useLocation();
|
||||
const history = useHistory();
|
||||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const agentPolicyIdFromParams = useMemo(
|
||||
const addAgentToPolicyIdFromParams = useMemo(
|
||||
() => queryParams.get('addAgentToPolicyId'),
|
||||
[queryParams]
|
||||
);
|
||||
|
@ -85,44 +54,27 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
() => queryParams.get('showAddAgentHelpForPolicyId'),
|
||||
[queryParams]
|
||||
);
|
||||
const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string | null>(
|
||||
agentPolicyIdFromParams
|
||||
);
|
||||
const [selectedTableIndex, setSelectedTableIndex] = useState<number | undefined>();
|
||||
|
||||
const { getPath, getHref } = useLink();
|
||||
const { getPath } = useLink();
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const packageInstallStatus = getPackageInstallStatus(name);
|
||||
const { pagination, pageSizeOptions, setPagination } = useUrlPagination();
|
||||
const { canUseMultipleAgentPolicies } = useMultipleAgentPolicies();
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
resendRequest: refreshPolicies,
|
||||
} = usePackagePoliciesWithAgentPolicy({
|
||||
page: pagination.currentPage,
|
||||
perPage: pagination.pageSize,
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`,
|
||||
});
|
||||
const { isPackagePolicyUpgradable } = useIsPackagePolicyUpgradable();
|
||||
const { isAgentlessIntegration } = useAgentless();
|
||||
const canHaveAgentlessPolicies = useMemo(
|
||||
() => isAgentlessIntegration(packageInfo),
|
||||
[isAgentlessIntegration, packageInfo]
|
||||
);
|
||||
|
||||
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||
const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies;
|
||||
const canReadAgentPolicies = useAuthz().fleet.readAgentPolicies;
|
||||
|
||||
const packageAndAgentPolicies = useMemo((): Array<{
|
||||
agentPolicies: GetAgentPoliciesResponseItem[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}> => {
|
||||
if (!data?.items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newPolicies = data.items.map(({ agentPolicies, packagePolicy }, index) => {
|
||||
// Helper function to map raw policies data for consumption by the table
|
||||
const mapPoliciesData = useCallback(
|
||||
(
|
||||
{
|
||||
agentPolicies,
|
||||
packagePolicy,
|
||||
}: { agentPolicies: AgentPolicy[]; packagePolicy: PackagePolicy },
|
||||
index: number
|
||||
) => {
|
||||
const hasUpgrade = isPackagePolicyUpgradable(packagePolicy);
|
||||
|
||||
return {
|
||||
agentPolicies,
|
||||
packagePolicy: {
|
||||
|
@ -131,284 +83,204 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
},
|
||||
rowIndex: index,
|
||||
};
|
||||
});
|
||||
|
||||
return newPolicies;
|
||||
}, [data?.items, isPackagePolicyUpgradable]);
|
||||
|
||||
const showAddAgentHelpForPackagePolicyId = packageAndAgentPolicies.find(({ agentPolicies }) =>
|
||||
agentPolicies.find((agentPolicy) => agentPolicy.id === showAddAgentHelpForPolicyId)
|
||||
)?.packagePolicy?.id;
|
||||
// Handle the "add agent" link displayed in post-installation toast notifications in the case
|
||||
// where a user is clicking the link while on the package policies listing page
|
||||
useEffect(() => {
|
||||
const unlisten = history.listen((location) => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const addAgentToPolicyId = params.get('addAgentToPolicyId');
|
||||
|
||||
if (addAgentToPolicyId) {
|
||||
setFlyoutOpenForPolicyId(addAgentToPolicyId);
|
||||
}
|
||||
});
|
||||
|
||||
return () => unlisten();
|
||||
}, [history]);
|
||||
|
||||
const handleTableOnChange = useCallback(
|
||||
({ page }: CriteriaWithPagination<InMemoryPackagePolicyAndAgentPolicy>) => {
|
||||
setPagination({
|
||||
currentPage: page.index + 1,
|
||||
pageSize: page.size,
|
||||
});
|
||||
},
|
||||
[setPagination]
|
||||
);
|
||||
const canShowMultiplePoliciesCell =
|
||||
canUseMultipleAgentPolicies && canReadIntegrationPolicies && canReadAgentPolicies;
|
||||
const columns: Array<EuiTableFieldDataColumnType<InMemoryPackagePolicyAndAgentPolicy>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
field: 'packagePolicy.name',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', {
|
||||
defaultMessage: 'Integration policy',
|
||||
}),
|
||||
render(_, { agentPolicies, packagePolicy }) {
|
||||
return (
|
||||
<IntegrationDetailsLink packagePolicy={packagePolicy} agentPolicies={agentPolicies} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.package.version',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.version', {
|
||||
defaultMessage: 'Version',
|
||||
}),
|
||||
render(_version, { agentPolicies, packagePolicy }) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" wrap={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" className="eui-textNoWrap" data-test-subj="packageVersionText">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.packageVersion"
|
||||
defaultMessage="v{version}"
|
||||
values={{ version: _version }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{agentPolicies.length > 0 && packagePolicy.hasUpgrade && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
minWidth="0"
|
||||
href={`${getHref('upgrade_package_policy', {
|
||||
policyId: agentPolicies[0].id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}?from=integrations-policy-list`}
|
||||
data-test-subj="integrationPolicyUpgradeBtn"
|
||||
isDisabled={!canWriteIntegrationPolicies}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton"
|
||||
defaultMessage="Upgrade"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.policy_ids',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentPolicy', {
|
||||
defaultMessage: 'Agent policies',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(ids, { agentPolicies, packagePolicy }) {
|
||||
return agentPolicies.length > 0 ? (
|
||||
canShowMultiplePoliciesCell ? (
|
||||
<MultipleAgentPoliciesSummaryLine
|
||||
policies={agentPolicies}
|
||||
packagePolicyId={packagePolicy.id}
|
||||
onAgentPoliciesChange={refreshPolicies}
|
||||
/>
|
||||
) : (
|
||||
<AgentPolicySummaryLine policy={agentPolicies[0]} />
|
||||
)
|
||||
) : ids.length === 0 ? (
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.noAgentPolicies"
|
||||
defaultMessage="No agent policies"
|
||||
/>
|
||||
</EuiText>
|
||||
) : (
|
||||
<EuiText color="subdued" size="xs">
|
||||
<EuiIcon size="m" type="warning" color="warning" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.agentPolicyDeletedWarning"
|
||||
defaultMessage="Policy not found"
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.updated_by',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedBy', {
|
||||
defaultMessage: 'Last updated by',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(updatedBy) {
|
||||
return <Persona size="s" name={updatedBy} title={updatedBy} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packagePolicy.updated_at',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedAt', {
|
||||
defaultMessage: 'Last updated',
|
||||
}),
|
||||
truncateText: true,
|
||||
render(updatedAt: InMemoryPackagePolicyAndAgentPolicy['packagePolicy']['updated_at']) {
|
||||
return (
|
||||
<span className="eui-textTruncate" title={updatedAt}>
|
||||
<FormattedRelative value={updatedAt} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentCount', {
|
||||
defaultMessage: 'Agents',
|
||||
}),
|
||||
render({ agentPolicies, packagePolicy, rowIndex }: InMemoryPackagePolicyAndAgentPolicy) {
|
||||
if (agentPolicies.length === 0) {
|
||||
return (
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.noAgents"
|
||||
defaultMessage="No agents"
|
||||
/>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PackagePolicyAgentsCell
|
||||
agentPolicies={agentPolicies}
|
||||
onAddAgent={() => {
|
||||
setSelectedTableIndex(rowIndex);
|
||||
setFlyoutOpenForPolicyId(agentPolicies[0].id);
|
||||
}}
|
||||
hasHelpPopover={showAddAgentHelpForPackagePolicyId === packagePolicy.id}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
width: '8ch',
|
||||
align: 'right',
|
||||
render({ agentPolicies, packagePolicy }) {
|
||||
const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies
|
||||
return (
|
||||
<PackagePolicyActionsMenu
|
||||
agentPolicies={agentPolicies}
|
||||
packagePolicy={packagePolicy}
|
||||
showAddAgent={true}
|
||||
upgradePackagePolicyHref={
|
||||
agentPolicy
|
||||
? `${getHref('upgrade_package_policy', {
|
||||
policyId: agentPolicy.id,
|
||||
packagePolicyId: packagePolicy.id,
|
||||
})}?from=integrations-policy-list`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
getHref,
|
||||
canWriteIntegrationPolicies,
|
||||
canShowMultiplePoliciesCell,
|
||||
showAddAgentHelpForPackagePolicyId,
|
||||
refreshPolicies,
|
||||
]
|
||||
[isPackagePolicyUpgradable]
|
||||
);
|
||||
|
||||
const noItemsMessage = useMemo(() => {
|
||||
return isLoading ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.loadingPoliciesMessage"
|
||||
defaultMessage="Loading integration policies…"
|
||||
/>
|
||||
) : undefined;
|
||||
}, [isLoading]);
|
||||
// States and data for agent-based policies table
|
||||
// If agentless is not supported or not an agentless integration, skip the
|
||||
// conditional in the kuery
|
||||
const {
|
||||
pagination: agentBasedPagination,
|
||||
pageSizeOptions: agentBasedPageSizeOptions,
|
||||
setPagination: agentBasedSetPagination,
|
||||
} = usePagination();
|
||||
const [agentBasedPackageAndAgentPolicies, setAgentBasedPackageAndAgentPolicies] = useState<
|
||||
Array<{
|
||||
agentPolicies: GetAgentPoliciesResponseItem[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}>
|
||||
>([]);
|
||||
const {
|
||||
data: agentBasedData,
|
||||
isLoading: agentBasedIsLoading,
|
||||
resendRequest: refreshAgentBasedPolicies,
|
||||
} = usePackagePoliciesWithAgentPolicy({
|
||||
page: agentBasedPagination.currentPage,
|
||||
perPage: agentBasedPagination.pageSize,
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "${name}" ${
|
||||
canHaveAgentlessPolicies
|
||||
? `AND NOT ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.supports_agentless: true`
|
||||
: ``
|
||||
}`,
|
||||
});
|
||||
useEffect(() => {
|
||||
setAgentBasedPackageAndAgentPolicies(
|
||||
!agentBasedData?.items ? [] : agentBasedData.items.map(mapPoliciesData)
|
||||
);
|
||||
}, [agentBasedData, mapPoliciesData]);
|
||||
|
||||
const tablePagination = useMemo(() => {
|
||||
return {
|
||||
pageIndex: pagination.currentPage - 1,
|
||||
pageSize: pagination.pageSize,
|
||||
totalItemCount: data?.total ?? 0,
|
||||
pageSizeOptions,
|
||||
};
|
||||
}, [data?.total, pageSizeOptions, pagination.currentPage, pagination.pageSize]);
|
||||
// States and data for agentless policies table
|
||||
// If agentless is not supported or not an agentless integration, this block and
|
||||
// initial request is unnessary but reduces code complexity
|
||||
const {
|
||||
pagination: agentlessPagination,
|
||||
pageSizeOptions: agentlessPageSizeOptions,
|
||||
setPagination: agentlessSetPagination,
|
||||
} = usePagination();
|
||||
const [agentlessPackageAndAgentPolicies, setAgentlessPackageAndAgentPolicies] = useState<
|
||||
Array<{
|
||||
agentPolicies: GetAgentPoliciesResponseItem[];
|
||||
packagePolicy: InMemoryPackagePolicy;
|
||||
rowIndex: number;
|
||||
}>
|
||||
>([]);
|
||||
const {
|
||||
data: agentlessData,
|
||||
isLoading: agentlessIsLoading,
|
||||
resendRequest: refreshAgentlessPolicies,
|
||||
} = usePackagePoliciesWithAgentPolicy({
|
||||
page: agentlessPagination.currentPage,
|
||||
perPage: agentlessPagination.pageSize,
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "${name}" AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.supports_agentless: true`,
|
||||
});
|
||||
useEffect(() => {
|
||||
setAgentlessPackageAndAgentPolicies(
|
||||
!agentlessData?.items ? [] : agentlessData.items.map(mapPoliciesData)
|
||||
);
|
||||
}, [agentlessData, mapPoliciesData]);
|
||||
|
||||
// if they arrive at this page and the package is not installed, send them to overview
|
||||
// this happens if they arrive with a direct url or they uninstall while on this tab
|
||||
// Check flyoutOpenForPolicyId otherwise right after installing a new integration the flyout won't open
|
||||
if (packageInstallStatus.status !== InstallStatus.installed && !flyoutOpenForPolicyId) {
|
||||
// Check `addAgentToPolicyIdFromParams` otherwise right after installing a new integration the flyout won't open
|
||||
if (packageInstallStatus.status !== InstallStatus.installed && !addAgentToPolicyIdFromParams) {
|
||||
return (
|
||||
<Redirect to={getPath('integration_details_overview', { pkgkey: `${name}-${version}` })} />
|
||||
);
|
||||
}
|
||||
|
||||
const selectedPolicies =
|
||||
selectedTableIndex !== undefined ? packageAndAgentPolicies[selectedTableIndex] : undefined;
|
||||
|
||||
const agentPolicies = selectedPolicies?.agentPolicies;
|
||||
const packagePolicy = selectedPolicies?.packagePolicy;
|
||||
const flyoutPolicy = agentPolicies?.length === 1 ? agentPolicies[0] : undefined;
|
||||
|
||||
return (
|
||||
<AgentPolicyRefreshContext.Provider value={{ refresh: refreshPolicies }}>
|
||||
<AgentPolicyRefreshContext.Provider
|
||||
value={{
|
||||
refresh: () => {
|
||||
refreshAgentBasedPolicies();
|
||||
refreshAgentlessPolicies();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<SideBarColumn grow={1} />
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiBasicTable
|
||||
items={packageAndAgentPolicies || []}
|
||||
columns={columns}
|
||||
loading={isLoading}
|
||||
data-test-subj="integrationPolicyTable"
|
||||
pagination={tablePagination}
|
||||
onChange={handleTableOnChange}
|
||||
noItemsMessage={noItemsMessage}
|
||||
/>
|
||||
{!canHaveAgentlessPolicies ? (
|
||||
<AgentBasedPackagePoliciesTable
|
||||
isLoading={agentBasedIsLoading}
|
||||
packagePolicies={agentBasedPackageAndAgentPolicies}
|
||||
packagePoliciesTotal={agentBasedData?.total ?? 0}
|
||||
refreshPackagePolicies={refreshAgentBasedPolicies}
|
||||
pagination={{
|
||||
pagination: agentBasedPagination,
|
||||
pageSizeOptions: agentBasedPageSizeOptions,
|
||||
setPagination: agentBasedSetPagination,
|
||||
}}
|
||||
addAgentToPolicyIdFromParams={addAgentToPolicyIdFromParams}
|
||||
showAddAgentHelpForPolicyId={showAddAgentHelpForPolicyId}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EuiAccordion
|
||||
id="agentBasedAccordion"
|
||||
initialIsOpen={true}
|
||||
buttonContent={
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.agentlessHeader"
|
||||
defaultMessage="Agentless"
|
||||
/>
|
||||
</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued" size="m">
|
||||
<h3>{agentlessData?.total ?? 0}</h3>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPanel hasBorder={true} hasShadow={false}>
|
||||
<AgentlessPackagePoliciesTable
|
||||
isLoading={agentlessIsLoading}
|
||||
packagePolicies={agentlessPackageAndAgentPolicies}
|
||||
packagePoliciesTotal={agentlessData?.total ?? 0}
|
||||
refreshPackagePolicies={refreshAgentlessPolicies}
|
||||
pagination={{
|
||||
pagination: agentlessPagination,
|
||||
pageSizeOptions: agentlessPageSizeOptions,
|
||||
setPagination: agentlessSetPagination,
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiAccordion
|
||||
id="agentBasedAccordion"
|
||||
initialIsOpen={true}
|
||||
buttonContent={
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.integrationList.agentBasedHeader"
|
||||
defaultMessage="Agent-based"
|
||||
/>
|
||||
</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued" size="m">
|
||||
<h3>{agentBasedData?.total ?? 0}</h3>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPanel hasBorder={true} hasShadow={false}>
|
||||
<AgentBasedPackagePoliciesTable
|
||||
isLoading={agentBasedIsLoading}
|
||||
packagePolicies={agentBasedPackageAndAgentPolicies}
|
||||
packagePoliciesTotal={agentBasedData?.total ?? 0}
|
||||
refreshPackagePolicies={refreshAgentBasedPolicies}
|
||||
pagination={{
|
||||
pagination: agentBasedPagination,
|
||||
pageSizeOptions: agentBasedPageSizeOptions,
|
||||
setPagination: agentBasedSetPagination,
|
||||
}}
|
||||
addAgentToPolicyIdFromParams={addAgentToPolicyIdFromParams}
|
||||
showAddAgentHelpForPolicyId={showAddAgentHelpForPolicyId}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiAccordion>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{flyoutOpenForPolicyId && agentPolicies && !isLoading && (
|
||||
<AgentEnrollmentFlyout
|
||||
onClose={() => {
|
||||
setFlyoutOpenForPolicyId(null);
|
||||
const { addAgentToPolicyId, ...rest } = parse(search);
|
||||
history.replace({ search: stringify(rest) });
|
||||
}}
|
||||
agentPolicy={flyoutPolicy}
|
||||
selectedAgentPolicies={agentPolicies}
|
||||
isIntegrationFlow={true}
|
||||
installedPackagePolicy={{
|
||||
name: packagePolicy?.package?.name || '',
|
||||
version: packagePolicy?.package?.version || '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AgentPolicyRefreshContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -30,7 +30,7 @@ export const ConfirmIncomingData: React.FunctionComponent<Props> = ({
|
|||
setAgentDataConfirmed,
|
||||
troubleshootLink,
|
||||
}) => {
|
||||
const { incomingData, isLoading } = usePollingIncomingData(agentIds);
|
||||
const { incomingData, isLoading } = usePollingIncomingData({ agentIds });
|
||||
const isGuidedOnboardingActive = useIsGuidedOnboardingActive(installedPolicy?.name);
|
||||
const { guidedOnboarding } = useStartServices();
|
||||
|
||||
|
|
|
@ -75,18 +75,26 @@ export const useGetAgentIncomingData = (
|
|||
};
|
||||
|
||||
const POLLING_INTERVAL_MS = 5 * 1000; // 5 sec
|
||||
const POLLING_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
||||
export const POLLING_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
||||
|
||||
/**
|
||||
* Hook for polling incoming data for the selected agent policy.
|
||||
* Hook for polling incoming data for the selected agent(s).
|
||||
* @param agentIds
|
||||
* @returns incomingData, isLoading
|
||||
*/
|
||||
export const usePollingIncomingData = (
|
||||
agentIds: string[],
|
||||
previewData?: boolean,
|
||||
stopPollingAfterPreviewLength: number = 0
|
||||
) => {
|
||||
export const usePollingIncomingData = ({
|
||||
agentIds,
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
previewData,
|
||||
stopPollingAfterPreviewLength = 0,
|
||||
}: {
|
||||
agentIds: string[];
|
||||
pkgName?: string;
|
||||
pkgVersion?: string;
|
||||
previewData?: boolean;
|
||||
stopPollingAfterPreviewLength?: number;
|
||||
}) => {
|
||||
const timeout = useRef<number | undefined>(undefined);
|
||||
const [result, setResult] = useState<{
|
||||
incomingData: IncomingDataList[];
|
||||
|
@ -117,7 +125,12 @@ export const usePollingIncomingData = (
|
|||
setHasReachedTimeout(true);
|
||||
}
|
||||
|
||||
const { data } = await sendGetAgentIncomingData({ agentsIds: agentIds, previewData });
|
||||
const { data } = await sendGetAgentIncomingData({
|
||||
agentsIds: agentIds,
|
||||
previewData,
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
});
|
||||
if (data?.items) {
|
||||
// filter out agents that have `data = false` and keep polling
|
||||
const filtered = data?.items.filter((item) => {
|
||||
|
@ -153,7 +166,15 @@ export const usePollingIncomingData = (
|
|||
return () => {
|
||||
isAborted = true;
|
||||
};
|
||||
}, [agentIds, result, previewData, stopPollingAfterPreviewLength, startedPollingAt]);
|
||||
}, [
|
||||
agentIds,
|
||||
result,
|
||||
previewData,
|
||||
stopPollingAfterPreviewLength,
|
||||
startedPollingAt,
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
]);
|
||||
|
||||
return {
|
||||
...result,
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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 { act, waitFor } from '@testing-library/react';
|
||||
|
||||
import { sendGetAgents, useGetPackageInfoByKeyQuery } from '../../hooks';
|
||||
import { usePollingIncomingData } from '../agent_enrollment_flyout/use_get_agent_incoming_data';
|
||||
import { createIntegrationsTestRendererMock } from '../../mock';
|
||||
|
||||
import { AGENTS_PREFIX } from '../../constants';
|
||||
|
||||
import type { PackagePolicy } from '../../types';
|
||||
|
||||
import { AgentlessEnrollmentFlyout } from '.';
|
||||
|
||||
jest.mock('../../hooks', () => ({
|
||||
...jest.requireActual('../../hooks'),
|
||||
useGetPackageInfoByKeyQuery: jest.fn(),
|
||||
sendGetAgents: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../agent_enrollment_flyout/use_get_agent_incoming_data', () => ({
|
||||
usePollingIncomingData: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockSendGetAgents = sendGetAgents as jest.Mock;
|
||||
const mockUseGetPackageInfoByKeyQuery = useGetPackageInfoByKeyQuery as jest.Mock;
|
||||
const mockUsePollingIncomingData = usePollingIncomingData as jest.Mock;
|
||||
|
||||
describe('AgentlessEnrollmentFlyout', () => {
|
||||
const onClose = jest.fn();
|
||||
const packagePolicy: PackagePolicy = {
|
||||
id: 'test-package-policy-id',
|
||||
name: 'test-package-policy',
|
||||
namespace: 'default',
|
||||
policy_ids: ['test-policy-id'],
|
||||
policy_id: 'test-policy-id',
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
package: { name: 'test-package', title: 'Test Package', version: '1.0.0' },
|
||||
inputs: [{ enabled: true, policy_template: 'test-template', type: 'test-type', streams: [] }],
|
||||
revision: 1,
|
||||
created_at: '',
|
||||
created_by: '',
|
||||
updated_at: '',
|
||||
updated_by: '',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockSendGetAgents.mockResolvedValue({ data: { items: [] } });
|
||||
mockUseGetPackageInfoByKeyQuery.mockReturnValue({ data: { item: { title: 'Test Package' } } });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the flyout with initial loading state', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const { getByText } = renderer.render(
|
||||
<AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} />
|
||||
);
|
||||
await act(async () => {
|
||||
expect(getByText('Confirm agentless enrollment')).toBeInTheDocument();
|
||||
expect(getByText('Step 1 is loading')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText('Listening for agentless connection... this could take several minutes')
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Confirm incoming data')).toBeInTheDocument();
|
||||
expect(getByText('Step 2 is disabled')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('updates step statuses when agent deployment fails', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const agentData = { status: 'error' };
|
||||
mockSendGetAgents.mockResolvedValueOnce({ data: { items: [agentData] } });
|
||||
|
||||
const { getByText } = renderer.render(
|
||||
<AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Confirm agentless enrollment')).toBeInTheDocument();
|
||||
expect(getByText('Step 1 has errors')).toBeInTheDocument();
|
||||
expect(getByText('Agentless deployment failed')).toBeInTheDocument();
|
||||
expect(getByText('Confirm incoming data')).toBeInTheDocument();
|
||||
expect(getByText('Step 2 is disabled')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches agents data on mount and sets step statuses when agent deployment succeeds', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
const agentData = { status: 'online' };
|
||||
mockSendGetAgents.mockResolvedValueOnce({ data: { items: [agentData] } });
|
||||
mockUsePollingIncomingData.mockReturnValue({ incomingData: [], hasReachedTimeout: false });
|
||||
|
||||
const { getByText } = renderer.render(
|
||||
<AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSendGetAgents).toHaveBeenCalledWith({
|
||||
kuery: `${AGENTS_PREFIX}.policy_id: "test-policy-id"`,
|
||||
});
|
||||
expect(getByText('Confirm agentless enrollment')).toBeInTheDocument();
|
||||
expect(getByText('Step 1 is complete')).toBeInTheDocument();
|
||||
expect(getByText('Agentless deployment was successful')).toBeInTheDocument();
|
||||
expect(getByText('Confirm incoming data')).toBeInTheDocument();
|
||||
expect(getByText('Step 2 is loading')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows confirm data step as failed when timeout has been reached', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
mockSendGetAgents.mockResolvedValueOnce({ data: { items: [{ status: 'online' }] } });
|
||||
mockUsePollingIncomingData.mockReturnValue({ incomingData: [], hasReachedTimeout: true });
|
||||
|
||||
const { getByText } = renderer.render(
|
||||
<AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Step 1 is complete')).toBeInTheDocument();
|
||||
expect(getByText('Confirm incoming data')).toBeInTheDocument();
|
||||
expect(getByText('Step 2 has errors')).toBeInTheDocument();
|
||||
expect(getByText('No incoming data received from agentless integration')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows confirm data step as successful when incoming data is received', async () => {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
mockSendGetAgents.mockResolvedValueOnce({ data: { items: [{ status: 'online' }] } });
|
||||
mockUsePollingIncomingData.mockReturnValue({ incomingData: [{ data: 'test-data' }] });
|
||||
|
||||
const { getByText } = renderer.render(
|
||||
<AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Step 1 is complete')).toBeInTheDocument();
|
||||
expect(getByText('Confirm incoming data')).toBeInTheDocument();
|
||||
expect(getByText('Step 2 is complete')).toBeInTheDocument();
|
||||
expect(getByText('Incoming data received from agentless integration')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { EuiStepStatus } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiFlyoutFooter,
|
||||
EuiSteps,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { AGENTS_PREFIX, MAX_FLYOUT_WIDTH } from '../../constants';
|
||||
import type { Agent, AgentPolicy, PackagePolicy } from '../../types';
|
||||
import { sendGetAgents, useStartServices, useGetPackageInfoByKeyQuery } from '../../hooks';
|
||||
|
||||
import { AgentlessStepConfirmEnrollment } from './step_confirm_enrollment';
|
||||
import { AgentlessStepConfirmData } from './step_confirm_data';
|
||||
|
||||
const REFRESH_INTERVAL_MS = 30000;
|
||||
|
||||
/**
|
||||
* This component displays additional status details of an agentless agent enrolled
|
||||
* the chosen package policy (and its agent policy).
|
||||
* It also displays confirmation that the agentless agent is ingesting data from
|
||||
* the chosen package policy.
|
||||
*/
|
||||
export const AgentlessEnrollmentFlyout = ({
|
||||
onClose,
|
||||
packagePolicy,
|
||||
agentPolicy,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
packagePolicy: PackagePolicy;
|
||||
agentPolicy?: AgentPolicy;
|
||||
}) => {
|
||||
const core = useStartServices();
|
||||
const { notifications } = core;
|
||||
const [confirmEnrollmentStatus, setConfirmEnrollmentStatus] = useState<EuiStepStatus>('loading');
|
||||
const [confirmDataStatus, setConfirmDataStatus] = useState<EuiStepStatus>('disabled');
|
||||
const [agentData, setAgentData] = useState<Agent>();
|
||||
|
||||
// Clear agent data polling
|
||||
// Called when component is unmounted or when agent is healthy
|
||||
const agentDataInterval = useRef<NodeJS.Timeout>();
|
||||
const clearAgentDataPolling = useMemo(() => {
|
||||
return () => {
|
||||
if (agentDataInterval.current) {
|
||||
clearInterval(agentDataInterval.current);
|
||||
}
|
||||
};
|
||||
}, [agentDataInterval]);
|
||||
|
||||
// Fetch agent(s) data for the first associated agent policy
|
||||
// Polls every 30 seconds until agent is found and healthy
|
||||
useEffect(() => {
|
||||
const fetchAgents = async () => {
|
||||
const { data: agentsData, error } = await sendGetAgents({
|
||||
kuery: `${AGENTS_PREFIX}.policy_id: "${packagePolicy.policy_ids[0]}"`,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate(
|
||||
'xpack.fleet.epm.packageDetails.integrationList.agentlessStatusError',
|
||||
{
|
||||
defaultMessage: 'Error fetching agentless status information',
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (agentsData?.items?.[0]) {
|
||||
setAgentData(agentsData.items?.[0]);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAgents();
|
||||
agentDataInterval.current = setInterval(() => {
|
||||
fetchAgents();
|
||||
}, REFRESH_INTERVAL_MS);
|
||||
|
||||
return () => clearAgentDataPolling();
|
||||
}, [clearAgentDataPolling, notifications.toasts, packagePolicy.policy_ids]);
|
||||
|
||||
// Watches agent data and updates step statuses and clears polling when agent is healthy
|
||||
useEffect(() => {
|
||||
if (agentData) {
|
||||
if (agentData.status === 'online') {
|
||||
setConfirmEnrollmentStatus('complete');
|
||||
setConfirmDataStatus('loading');
|
||||
clearAgentDataPolling();
|
||||
} else if (agentData.status === 'error' || agentData.status === 'degraded') {
|
||||
setConfirmEnrollmentStatus('danger');
|
||||
setConfirmDataStatus('disabled');
|
||||
} else {
|
||||
setConfirmEnrollmentStatus('loading');
|
||||
setConfirmDataStatus('disabled');
|
||||
}
|
||||
} else {
|
||||
setConfirmEnrollmentStatus('loading');
|
||||
setConfirmDataStatus('disabled');
|
||||
}
|
||||
}, [agentData, clearAgentDataPolling]);
|
||||
|
||||
// Calculate integration title from the base package info and what
|
||||
// is configured on the package policy.
|
||||
const { data: packageInfoData } = useGetPackageInfoByKeyQuery(
|
||||
packagePolicy.package!.name,
|
||||
packagePolicy.package!.version,
|
||||
{
|
||||
prerelease: true,
|
||||
}
|
||||
);
|
||||
|
||||
const integrationTitle = useMemo(() => {
|
||||
if (packageInfoData?.item) {
|
||||
const enabledInputs = packagePolicy.inputs?.filter((input) => input.enabled);
|
||||
|
||||
// If only one input is enabled, find the input name from the package info and
|
||||
// and use that for integration title. Otherwise, use the package name.
|
||||
if (enabledInputs.length === 1 && enabledInputs[0].policy_template) {
|
||||
const policyTemplate = packageInfoData.item.policy_templates?.find(
|
||||
(template) => template.name === enabledInputs[0].policy_template
|
||||
);
|
||||
const input =
|
||||
policyTemplate && 'inputs' in policyTemplate
|
||||
? policyTemplate.inputs?.find((i) => i.type === enabledInputs[0].type)
|
||||
: null;
|
||||
return input?.title || packageInfoData.item.title;
|
||||
} else {
|
||||
return packageInfoData.item.title;
|
||||
}
|
||||
}
|
||||
return packagePolicy.name;
|
||||
}, [packageInfoData, packagePolicy]);
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
data-test-subj="agentlessEnrollmentFlyout"
|
||||
onClose={onClose}
|
||||
maxWidth={MAX_FLYOUT_WIDTH}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder aria-labelledby="FleetAgentlessEnrollmentFlyoutTitle">
|
||||
<EuiTitle size="m">
|
||||
<h2 id="FleetAgentlessEnrollmentFlyoutTitle">{packagePolicy.name}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiSteps
|
||||
steps={[
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.fleet.agentlessEnrollmentFlyout.stepConfirmEnrollmentTitle',
|
||||
{
|
||||
defaultMessage: 'Confirm agentless enrollment',
|
||||
}
|
||||
),
|
||||
children: (
|
||||
<AgentlessStepConfirmEnrollment
|
||||
agent={agentData}
|
||||
agentPolicy={agentPolicy}
|
||||
integrationTitle={integrationTitle}
|
||||
/>
|
||||
),
|
||||
status: confirmEnrollmentStatus,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.stepConfirmDataTitle', {
|
||||
defaultMessage: 'Confirm incoming data',
|
||||
}),
|
||||
children:
|
||||
agentData && confirmEnrollmentStatus === 'complete' ? (
|
||||
<AgentlessStepConfirmData
|
||||
agent={agentData}
|
||||
packagePolicy={packagePolicy}
|
||||
setConfirmDataStatus={setConfirmDataStatus}
|
||||
/>
|
||||
) : (
|
||||
<></> // Avoids React error about null children prop
|
||||
),
|
||||
status: confirmDataStatus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={onClose}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.closeFlyoutButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { EuiStepStatus } from '@elastic/eui';
|
||||
import { EuiText, EuiLink, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { useStartServices } from '../../hooks';
|
||||
import type { Agent, PackagePolicy } from '../../types';
|
||||
import {
|
||||
usePollingIncomingData,
|
||||
POLLING_TIMEOUT_MS,
|
||||
} from '../agent_enrollment_flyout/use_get_agent_incoming_data';
|
||||
|
||||
export const AgentlessStepConfirmData = ({
|
||||
agent,
|
||||
packagePolicy,
|
||||
setConfirmDataStatus,
|
||||
}: {
|
||||
agent: Agent;
|
||||
packagePolicy: PackagePolicy;
|
||||
setConfirmDataStatus: (status: EuiStepStatus) => void;
|
||||
}) => {
|
||||
const { docLinks } = useStartServices();
|
||||
const [overallState, setOverallState] = useState<'pending' | 'success' | 'failure'>('pending');
|
||||
|
||||
// Fetch integration data for the given agent and package policy
|
||||
const { incomingData, hasReachedTimeout } = usePollingIncomingData({
|
||||
agentIds: [agent.id],
|
||||
pkgName: packagePolicy.package!.name,
|
||||
pkgVersion: packagePolicy.package!.version,
|
||||
});
|
||||
|
||||
// Calculate overall UI state from polling data
|
||||
useEffect(() => {
|
||||
if (incomingData.length > 0) {
|
||||
setConfirmDataStatus('complete');
|
||||
setOverallState('success');
|
||||
} else if (hasReachedTimeout) {
|
||||
setConfirmDataStatus('danger');
|
||||
setOverallState('failure');
|
||||
} else {
|
||||
setConfirmDataStatus('loading');
|
||||
setOverallState('pending');
|
||||
}
|
||||
}, [incomingData, hasReachedTimeout, setConfirmDataStatus]);
|
||||
|
||||
if (overallState === 'success') {
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="success"
|
||||
title={i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.confirmData.successText', {
|
||||
defaultMessage: 'Incoming data received from agentless integration',
|
||||
})}
|
||||
iconType="check"
|
||||
/>
|
||||
);
|
||||
} else if (overallState === 'failure') {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.confirmData.failureText', {
|
||||
defaultMessage: 'No incoming data received from agentless integration',
|
||||
})}
|
||||
iconType="warning"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmData.failureHelperText"
|
||||
defaultMessage="No integration data receieved in the past {num} minutes. Check out the {troubleshootingGuideLink} for help."
|
||||
values={{
|
||||
num: POLLING_TIMEOUT_MS / 1000 / 60,
|
||||
troubleshootingGuideLink: (
|
||||
<EuiLink href={docLinks.links.fleet.troubleshooting} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmData.pendingHelperText.troubleshootingLinkLabel"
|
||||
defaultMessage="troubleshooting guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiButton, EuiPanel, EuiText, EuiLink, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import type { Agent, AgentPolicy } from '../../types';
|
||||
import { useStartServices } from '../../hooks';
|
||||
import { AgentDetailsIntegrations } from '../../applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations';
|
||||
|
||||
export const AgentlessStepConfirmEnrollment = ({
|
||||
agent,
|
||||
agentPolicy,
|
||||
integrationTitle,
|
||||
}: {
|
||||
agent?: Agent;
|
||||
agentPolicy?: AgentPolicy;
|
||||
integrationTitle: string;
|
||||
}) => {
|
||||
const { docLinks } = useStartServices();
|
||||
const [overallState, setOverallState] = useState<'pending' | 'success' | 'failure'>('pending');
|
||||
|
||||
// Calculate overall UI state from agent status
|
||||
useEffect(() => {
|
||||
if (agent && agent.status === 'online') {
|
||||
setOverallState('success');
|
||||
} else if (agent && (agent.status === 'error' || agent.status === 'degraded')) {
|
||||
setOverallState('failure');
|
||||
} else {
|
||||
setOverallState('pending');
|
||||
}
|
||||
}, [agent]);
|
||||
|
||||
if (overallState === 'success') {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.successText',
|
||||
{
|
||||
defaultMessage: 'Agentless deployment was successful',
|
||||
}
|
||||
)}
|
||||
iconType="check"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.successHelperText"
|
||||
defaultMessage="{integrationTitle} agentless integration has been successfully established. You can now seamlessly monitor and manage your {integrationTitle} resources without the need for any additional agents."
|
||||
values={{
|
||||
integrationTitle,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
} else if (overallState === 'failure') {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate(
|
||||
'xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.failureText',
|
||||
{
|
||||
defaultMessage: 'Agentless deployment failed',
|
||||
}
|
||||
)}
|
||||
iconType="warning"
|
||||
>
|
||||
{agent?.last_checkin_message && <p>{agent.last_checkin_message}</p>}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.failureHelperText"
|
||||
defaultMessage="{integrationTitle} agentless integration failed to establish. Check out the {troubleshootingGuideLink} for help."
|
||||
values={{
|
||||
integrationTitle,
|
||||
troubleshootingGuideLink: (
|
||||
<EuiLink href={docLinks.links.fleet.troubleshooting} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText.troubleshootingLinkLabel"
|
||||
defaultMessage="troubleshooting guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
{agent && agentPolicy && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<AgentDetailsIntegrations agent={agent} agentPolicy={agentPolicy} linkToLogs={false} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPanel color="subdued" paddingSize="xl" className="eui-textCenter">
|
||||
<EuiButton disabled={true} size="s" isLoading={true}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingText"
|
||||
defaultMessage="Listening for agentless connection... this could take several minutes"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText"
|
||||
defaultMessage="Getting ready to connect with your cloud account and confirm incoming data. If you're having trouble connecting, check out the {troubleshootingGuideLink}. You can track the latest status from {policyPagePath} Status column."
|
||||
values={{
|
||||
troubleshootingGuideLink: (
|
||||
<EuiLink href={docLinks.links.fleet.troubleshooting} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText.troubleshootingLinkLabel"
|
||||
defaultMessage="troubleshooting guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
policyPagePath: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText.policyPagePath"
|
||||
defaultMessage="Integration policies → Agentless Integrations"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -29,3 +29,4 @@ export { HeaderReleaseBadge, InlineReleaseBadge } from './release_badge';
|
|||
export { WithGuidedOnboardingTour } from './with_guided_onboarding_tour';
|
||||
export { UninstallCommandFlyout } from './uninstall_command_flyout';
|
||||
export { MultipleAgentPoliciesSummaryLine } from './multiple_agent_policy_summary_line';
|
||||
export { AgentlessEnrollmentFlyout } from './agentless_enrollment_flyout';
|
||||
|
|
|
@ -115,13 +115,12 @@ describe.skip('PackagePolicyActionsMenu', () => {
|
|||
useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: false });
|
||||
});
|
||||
|
||||
it('Should disable upgrade button if package does not have upgrade', async () => {
|
||||
it('Should not have upgrade button if package does not have upgrade', async () => {
|
||||
const agentPolicies = createMockAgentPolicies();
|
||||
const packagePolicy = createMockPackagePolicy({ hasUpgrade: false });
|
||||
const { utils } = renderMenu({ agentPolicies, packagePolicy });
|
||||
await act(async () => {
|
||||
const upgradeButton = utils.getByTestId('PackagePolicyActionsUpgradeItem');
|
||||
expect(upgradeButton).toBeDisabled();
|
||||
expect(utils.queryByTestId('PackagePolicyActionsUpgradeItem')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -108,24 +108,27 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
|||
defaultMessage="Edit integration"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="PackagePolicyActionsUpgradeItem"
|
||||
disabled={
|
||||
!packagePolicy.hasUpgrade ||
|
||||
!canWriteIntegrationPolicies ||
|
||||
!upgradePackagePolicyHref ||
|
||||
agentPolicy?.supports_agentless === true
|
||||
}
|
||||
icon="refresh"
|
||||
href={upgradePackagePolicyHref}
|
||||
key="packagePolicyUpgrade"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeActionTitle"
|
||||
data-test-subj="UpgradeIntegrationPolicy"
|
||||
defaultMessage="Upgrade integration policy"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
...(packagePolicy.hasUpgrade
|
||||
? [
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="PackagePolicyActionsUpgradeItem"
|
||||
disabled={
|
||||
!canWriteIntegrationPolicies ||
|
||||
!upgradePackagePolicyHref ||
|
||||
agentPolicy?.supports_agentless === true
|
||||
}
|
||||
icon="refresh"
|
||||
href={upgradePackagePolicyHref}
|
||||
key="packagePolicyUpgrade"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeActionTitle"
|
||||
data-test-subj="UpgradeIntegrationPolicy"
|
||||
defaultMessage="Upgrade integration policy"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
]
|
||||
: []),
|
||||
// FIXME: implement Copy package policy action
|
||||
// <EuiContextMenuItem disabled icon="copy" onClick={() => {}} key="packagePolicyCopy">
|
||||
// <FormattedMessage
|
||||
|
|
|
@ -46,6 +46,8 @@ import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics'
|
|||
import { getAgentStatusForAgentPolicy } from '../../services/agents';
|
||||
import { isAgentInNamespace } from '../../services/spaces/agent_namespaces';
|
||||
import { getCurrentNamespace } from '../../services/spaces/get_current_namespace';
|
||||
import { getPackageInfo } from '../../services/epm/packages';
|
||||
import { generateTemplateIndexPattern } from '../../services/epm/elasticsearch/template/template';
|
||||
import { buildAgentStatusRuntimeField } from '../../services/agents/build_status_runtime_field';
|
||||
|
||||
async function verifyNamespace(agent: Agent, namespace?: string) {
|
||||
|
@ -308,17 +310,32 @@ export const getAgentDataHandler: RequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
const coreContext = await context.core;
|
||||
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
||||
|
||||
const returnDataPreview = request.query.previewData;
|
||||
const agentIds = isStringArray(request.query.agentsIds)
|
||||
const agentsIds = isStringArray(request.query.agentsIds)
|
||||
? request.query.agentsIds
|
||||
: [request.query.agentsIds];
|
||||
const { pkgName, pkgVersion, previewData: returnDataPreview } = request.query;
|
||||
|
||||
const { items, dataPreview } = await AgentService.getIncomingDataByAgentsId(
|
||||
// If a package is specified, get data stream patterns for that package
|
||||
// and scope incoming data query to that pattern
|
||||
let dataStreamPattern: string | undefined;
|
||||
if (pkgName && pkgVersion) {
|
||||
const packageInfo = await getPackageInfo({
|
||||
savedObjectsClient: coreContext.savedObjects.client,
|
||||
prerelease: true,
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
});
|
||||
dataStreamPattern = (packageInfo.data_streams || [])
|
||||
.map((ds) => generateTemplateIndexPattern(ds))
|
||||
.join(',');
|
||||
}
|
||||
|
||||
const { items, dataPreview } = await AgentService.getIncomingDataByAgentsId({
|
||||
esClient,
|
||||
agentIds,
|
||||
returnDataPreview
|
||||
);
|
||||
agentsIds,
|
||||
dataStreamPattern,
|
||||
returnDataPreview,
|
||||
});
|
||||
|
||||
const body = { items, dataPreview };
|
||||
|
||||
|
|
|
@ -614,6 +614,7 @@ export const getSavedObjectTypes = (
|
|||
},
|
||||
secret_references: { properties: { id: { type: 'keyword' } } },
|
||||
overrides: { type: 'flattened', index: false },
|
||||
supports_agentless: { type: 'boolean' },
|
||||
revision: { type: 'integer' },
|
||||
updated_at: { type: 'date' },
|
||||
updated_by: { type: 'keyword' },
|
||||
|
@ -774,6 +775,16 @@ export const getSavedObjectTypes = (
|
|||
},
|
||||
],
|
||||
},
|
||||
'16': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {
|
||||
supports_agentless: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
'7.10.0': migratePackagePolicyToV7100,
|
||||
|
@ -829,6 +840,7 @@ export const getSavedObjectTypes = (
|
|||
},
|
||||
secret_references: { properties: { id: { type: 'keyword' } } },
|
||||
overrides: { type: 'flattened', index: false },
|
||||
supports_agentless: { type: 'boolean' },
|
||||
revision: { type: 'integer' },
|
||||
updated_at: { type: 'date' },
|
||||
updated_by: { type: 'keyword' },
|
||||
|
@ -848,6 +860,16 @@ export const getSavedObjectTypes = (
|
|||
},
|
||||
],
|
||||
},
|
||||
'2': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {
|
||||
supports_agentless: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PACKAGES_SAVED_OBJECT_TYPE]: {
|
||||
|
|
|
@ -177,11 +177,17 @@ export async function getAgentStatusForAgentPolicy(
|
|||
};
|
||||
}
|
||||
|
||||
export async function getIncomingDataByAgentsId(
|
||||
esClient: ElasticsearchClient,
|
||||
agentsIds: string[],
|
||||
returnDataPreview: boolean = false
|
||||
) {
|
||||
export async function getIncomingDataByAgentsId({
|
||||
esClient,
|
||||
agentsIds,
|
||||
dataStreamPattern = DATA_STREAM_INDEX_PATTERN,
|
||||
returnDataPreview = false,
|
||||
}: {
|
||||
esClient: ElasticsearchClient;
|
||||
agentsIds: string[];
|
||||
dataStreamPattern?: string;
|
||||
returnDataPreview?: boolean;
|
||||
}) {
|
||||
const logger = appContextService.getLogger();
|
||||
|
||||
try {
|
||||
|
@ -189,7 +195,7 @@ export async function getIncomingDataByAgentsId(
|
|||
body: {
|
||||
index: [
|
||||
{
|
||||
names: [DATA_STREAM_INDEX_PATTERN],
|
||||
names: [dataStreamPattern],
|
||||
privileges: ['read'],
|
||||
},
|
||||
],
|
||||
|
@ -203,7 +209,7 @@ export async function getIncomingDataByAgentsId(
|
|||
const searchResult = await retryTransientEsErrors(
|
||||
() =>
|
||||
esClient.search({
|
||||
index: DATA_STREAM_INDEX_PATTERN,
|
||||
index: dataStreamPattern,
|
||||
allow_partial_search_results: true,
|
||||
_source: returnDataPreview,
|
||||
timeout: '5s',
|
||||
|
@ -244,9 +250,9 @@ export async function getIncomingDataByAgentsId(
|
|||
if (!searchResult.aggregations?.agent_ids) {
|
||||
return {
|
||||
items: agentsIds.map((id) => {
|
||||
return { items: { [id]: { data: false } } };
|
||||
return { [id]: { data: false } };
|
||||
}),
|
||||
data: [],
|
||||
dataPreview: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1929,6 +1929,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
|
|||
output_id: newPolicy.output_id,
|
||||
inputs: newPolicy.inputs[0]?.streams ? newPolicy.inputs : inputs,
|
||||
vars: newPolicy.vars || newPP.vars,
|
||||
supports_agentless: newPolicy.supports_agentless,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,16 @@ export const PackagePolicyBaseSchema = {
|
|||
),
|
||||
])
|
||||
),
|
||||
supports_agentless: schema.maybe(
|
||||
schema.nullable(
|
||||
schema.boolean({
|
||||
defaultValue: false,
|
||||
meta: {
|
||||
description: 'Indicates whether the package policy belongs to an agentless agent policy.',
|
||||
},
|
||||
})
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
export const NewPackagePolicySchema = schema.object({
|
||||
|
@ -286,6 +296,16 @@ export const SimplifiedPackagePolicyBaseSchema = schema.object({
|
|||
output_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
|
||||
vars: schema.maybe(SimplifiedVarsSchema),
|
||||
inputs: SimplifiedPackagePolicyInputsSchema,
|
||||
supports_agentless: schema.maybe(
|
||||
schema.nullable(
|
||||
schema.boolean({
|
||||
defaultValue: false,
|
||||
meta: {
|
||||
description: 'Indicates whether the package policy belongs to an agentless agent policy.',
|
||||
},
|
||||
})
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
export const SimplifiedPackagePolicyPreconfiguredSchema = SimplifiedPackagePolicyBaseSchema.extends(
|
||||
|
|
|
@ -527,6 +527,8 @@ export const GetAgentStatusResponseSchema = schema.object({
|
|||
export const GetAgentDataRequestSchema = {
|
||||
query: schema.object({
|
||||
agentsIds: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]),
|
||||
pkgName: schema.maybe(schema.string()),
|
||||
pkgVersion: schema.maybe(schema.string()),
|
||||
previewData: schema.boolean({ defaultValue: false }),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -65,12 +65,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await cisIntegration.navigateToIntegrationCspList();
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be(
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessIntegration()).to.be(
|
||||
integrationPolicyName
|
||||
);
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be(
|
||||
`Agentless policy for ${integrationPolicyName}`
|
||||
);
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessStatus()).to.be('Pending');
|
||||
});
|
||||
|
||||
it(`should show setup technology selector in edit mode`, async () => {
|
||||
|
@ -97,7 +95,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await cisIntegration.navigateToIntegrationCspList();
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await cisIntegration.navigateToEditIntegrationPage();
|
||||
await cisIntegration.navigateToEditAgentlessIntegrationPage();
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
expect(await cisIntegration.showSetupTechnologyComponent()).to.be(true);
|
||||
|
|
|
@ -218,6 +218,10 @@ export function AddCisIntegrationFormPageProvider({
|
|||
await testSubjects.click('integrationNameLink');
|
||||
};
|
||||
|
||||
const navigateToEditAgentlessIntegrationPage = async () => {
|
||||
await testSubjects.click('agentlessIntegrationNameLink');
|
||||
};
|
||||
|
||||
const navigateToAddIntegrationKspmPage = async (space?: string) => {
|
||||
const options = space
|
||||
? {
|
||||
|
@ -485,7 +489,7 @@ export function AddCisIntegrationFormPageProvider({
|
|||
await navigateToIntegrationCspList();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await navigateToEditIntegrationPage();
|
||||
await navigateToEditAgentlessIntegrationPage();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
// Fill out form to edit an agentless integration
|
||||
|
@ -498,7 +502,7 @@ export function AddCisIntegrationFormPageProvider({
|
|||
// Check if the Direct Access Key is updated package policy api with successful toast
|
||||
expect(await testSubjects.exists('policyUpdateSuccessToast')).to.be(true);
|
||||
|
||||
await navigateToEditIntegrationPage();
|
||||
await navigateToEditAgentlessIntegrationPage();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
};
|
||||
|
||||
|
@ -511,12 +515,23 @@ export function AddCisIntegrationFormPageProvider({
|
|||
return await integration.getVisibleText();
|
||||
};
|
||||
|
||||
const getFirstCspmIntegrationPageAgentlessIntegration = async () => {
|
||||
const integration = await testSubjects.find('agentlessIntegrationNameLink');
|
||||
return await integration.getVisibleText();
|
||||
};
|
||||
|
||||
const getFirstCspmIntegrationPageAgent = async () => {
|
||||
const agent = await testSubjects.find('agentPolicyNameLink');
|
||||
// this is assuming that the agent was just created therefor should be the first element
|
||||
return await agent.getVisibleText();
|
||||
};
|
||||
|
||||
const getFirstCspmIntegrationPageAgentlessStatus = async () => {
|
||||
const agent = await testSubjects.find('agentlessStatusBadge');
|
||||
// this is assuming that the agent was just created therefor should be the first element
|
||||
return await agent.getVisibleText();
|
||||
};
|
||||
|
||||
const getAgentBasedPolicyValue = async () => {
|
||||
const agentName = await testSubjects.find('createAgentPolicyNameField');
|
||||
return await agentName.getAttribute('value');
|
||||
|
@ -568,10 +583,13 @@ export function AddCisIntegrationFormPageProvider({
|
|||
testSubjectIds,
|
||||
inputIntegrationName,
|
||||
getFirstCspmIntegrationPageIntegration,
|
||||
getFirstCspmIntegrationPageAgentlessIntegration,
|
||||
getFirstCspmIntegrationPageAgent,
|
||||
getFirstCspmIntegrationPageAgentlessStatus,
|
||||
getAgentBasedPolicyValue,
|
||||
showSuccessfulToast,
|
||||
showSetupTechnologyComponent,
|
||||
navigateToEditIntegrationPage,
|
||||
navigateToEditAgentlessIntegrationPage,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -334,5 +334,31 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'xxxx')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should return incoming data status for specified agents', async () => {
|
||||
// force install the system package to override package verification
|
||||
await supertest
|
||||
.post(`/api/fleet/epm/packages/system/1.50.0`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({ force: true })
|
||||
.expect(200);
|
||||
|
||||
const { body: apiResponse1 } = await supertest
|
||||
.get(`/api/fleet/agent_status/data?agentsIds=agent1&agentsIds=agent2`)
|
||||
.expect(200);
|
||||
const { body: apiResponse2 } = await supertest
|
||||
.get(
|
||||
`/api/fleet/agent_status/data?agentsIds=agent1&agentsIds=agent2&pkgName=system&pkgVersion=1.50.0`
|
||||
)
|
||||
.expect(200);
|
||||
expect(apiResponse1).to.eql({
|
||||
items: [{ agent1: { data: false } }, { agent2: { data: false } }],
|
||||
dataPreview: [],
|
||||
});
|
||||
expect(apiResponse2).to.eql({
|
||||
items: [{ agent1: { data: false } }, { agent2: { data: false } }],
|
||||
dataPreview: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -78,12 +78,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await cisIntegration.navigateToIntegrationCspList();
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be(
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessIntegration()).to.be(
|
||||
integrationPolicyName
|
||||
);
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be(
|
||||
`Agentless policy for ${integrationPolicyName}`
|
||||
);
|
||||
expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessStatus()).to.be('Pending');
|
||||
});
|
||||
|
||||
it(`should create default agent-based agent`, async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue