diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 8ed34159e403b..a57b32e921778 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -15406,12 +15406,18 @@ "online": { "type": "number" }, + "orphaned": { + "type": "number" + }, "other": { "type": "number" }, "unenrolled": { "type": "number" }, + "uninstalled": { + "type": "number" + }, "updating": { "type": "number" } @@ -15946,7 +15952,9 @@ "unenrolling", "unenrolled", "updating", - "degraded" + "degraded", + "uninstalled", + "orphaned" ], "type": "string" }, @@ -17959,7 +17967,9 @@ "unenrolling", "unenrolled", "updating", - "degraded" + "degraded", + "uninstalled", + "orphaned" ], "type": "string" }, @@ -18439,7 +18449,9 @@ "unenrolling", "unenrolled", "updating", - "degraded" + "degraded", + "uninstalled", + "orphaned" ], "type": "string" }, diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index dc9deb7397601..7892210aa0ecc 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -15406,12 +15406,18 @@ "online": { "type": "number" }, + "orphaned": { + "type": "number" + }, "other": { "type": "number" }, "unenrolled": { "type": "number" }, + "uninstalled": { + "type": "number" + }, "updating": { "type": "number" } @@ -15946,7 +15952,9 @@ "unenrolling", "unenrolled", "updating", - "degraded" + "degraded", + "uninstalled", + "orphaned" ], "type": "string" }, @@ -17959,7 +17967,9 @@ "unenrolling", "unenrolled", "updating", - "degraded" + "degraded", + "uninstalled", + "orphaned" ], "type": "string" }, @@ -18439,7 +18449,9 @@ "unenrolling", "unenrolled", "updating", - "degraded" + "degraded", + "uninstalled", + "orphaned" ], "type": "string" }, diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index dd0d129d31175..99813f9cf1182 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -15901,10 +15901,14 @@ paths: type: number online: type: number + orphaned: + type: number other: type: number unenrolled: type: number + uninstalled: + type: number updating: type: number required: @@ -16270,6 +16274,8 @@ paths: - unenrolled - updating - degraded + - uninstalled + - orphaned type: string tags: items: @@ -16717,6 +16723,8 @@ paths: - unenrolled - updating - degraded + - uninstalled + - orphaned type: string tags: items: @@ -17056,6 +17064,8 @@ paths: - unenrolled - updating - degraded + - uninstalled + - orphaned type: string tags: items: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 46f1090884871..d98feafb3e985 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -18031,10 +18031,14 @@ paths: type: number online: type: number + orphaned: + type: number other: type: number unenrolled: type: number + uninstalled: + type: number updating: type: number required: @@ -18398,6 +18402,8 @@ paths: - unenrolled - updating - degraded + - uninstalled + - orphaned type: string tags: items: @@ -18842,6 +18848,8 @@ paths: - unenrolled - updating - degraded + - uninstalled + - orphaned type: string tags: items: @@ -19180,6 +19188,8 @@ paths: - unenrolled - updating - degraded + - uninstalled + - orphaned type: string tags: items: diff --git a/x-pack/platform/plugins/shared/fleet/common/constants/agent.ts b/x-pack/platform/plugins/shared/fleet/common/constants/agent.ts index cf1e97090804e..2df9ef61b3ce7 100644 --- a/x-pack/platform/plugins/shared/fleet/common/constants/agent.ts +++ b/x-pack/platform/plugins/shared/fleet/common/constants/agent.ts @@ -46,6 +46,8 @@ export const AgentStatuses = [ 'unenrolled', 'updating', 'degraded', + 'uninstalled', + 'orphaned', ] as const; // Kueries for finding unprivileged and privileged agents diff --git a/x-pack/platform/plugins/shared/fleet/common/services/agent_status.ts b/x-pack/platform/plugins/shared/fleet/common/services/agent_status.ts index 42b586d7552ae..e53952202b4f1 100644 --- a/x-pack/platform/plugins/shared/fleet/common/services/agent_status.ts +++ b/x-pack/platform/plugins/shared/fleet/common/services/agent_status.ts @@ -36,6 +36,12 @@ export function getPreviousAgentStatusForOfflineAgents( export function buildKueryForUnenrolledAgents(): string { return 'status:unenrolled'; } +export function buildKueryForUninstalledAgents(): string { + return 'status:uninstalled'; +} +export function buildKueryForOrphanedAgents(): string { + return 'status:orphaned'; +} export function buildKueryForOnlineAgents(): string { return 'status:online'; diff --git a/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.test.ts b/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.test.ts index 49144a75b5691..5175d41c99cd5 100644 --- a/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.test.ts +++ b/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.test.ts @@ -20,14 +20,18 @@ describe('agentStatusesToSummary', () => { enrolling: 7, unenrolling: 8, unenrolled: 9, + orphaned: 0, + uninstalled: 0, }) ).toEqual({ healthy: 1, unhealthy: 5, + orphaned: 0, inactive: 4, offline: 5, updating: 21, unenrolled: 9, + uninstalled: 0, }); }); }); diff --git a/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.ts b/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.ts index 0c1f0311851d4..8df55505e9d8d 100644 --- a/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.ts +++ b/x-pack/platform/plugins/shared/fleet/common/services/agent_statuses_to_summary.ts @@ -17,5 +17,7 @@ export function agentStatusesToSummary( offline: statuses.offline, updating: statuses.updating + statuses.enrolling + statuses.unenrolling, unenrolled: statuses.unenrolled, + orphaned: statuses.orphaned, + uninstalled: statuses.uninstalled, }; } diff --git a/x-pack/platform/plugins/shared/fleet/common/types/models/agent.ts b/x-pack/platform/plugins/shared/fleet/common/types/models/agent.ts index ba1872bc4478f..abfaa66278d07 100644 --- a/x-pack/platform/plugins/shared/fleet/common/types/models/agent.ts +++ b/x-pack/platform/plugins/shared/fleet/common/types/models/agent.ts @@ -24,10 +24,12 @@ export type AgentStatus = AgentStatusTuple[number]; export type SimplifiedAgentStatus = | 'healthy' | 'unhealthy' + | 'orphaned' | 'updating' | 'offline' | 'inactive' - | 'unenrolled'; + | 'unenrolled' + | 'uninstalled'; export type AgentActionType = | 'UNENROLL' diff --git a/x-pack/platform/plugins/shared/fleet/cypress/e2e/agents/agent_list.cy.ts b/x-pack/platform/plugins/shared/fleet/cypress/e2e/agents/agent_list.cy.ts index 55c0e61057c9d..347fbcb21bba9 100644 --- a/x-pack/platform/plugins/shared/fleet/cypress/e2e/agents/agent_list.cy.ts +++ b/x-pack/platform/plugins/shared/fleet/cypress/e2e/agents/agent_list.cy.ts @@ -123,6 +123,8 @@ describe('View agents list', () => { updating: 0, other: 0, events: 0, + orphaned: 0, + uninstalled: 0, }); cy.intercept('GET', /\/api\/fleet\/agents/).as('getAgents'); }); @@ -210,10 +212,11 @@ describe('View agents list', () => { cy.get('li').contains('Unhealthy').click(); cy.get('li').contains('Updating').click(); cy.get('li').contains('Offline').click(); + cy.get('li').contains('Orphaned').click(); cy.getBySel(FLEET_AGENT_LIST_PAGE.STATUS_FILTER).click(); cy.wait('@getAgents'); }; - it('should filter on healthy (16 result)', () => { + it('should filter on healthy (18 results)', () => { cy.visit('/app/fleet/agents'); clearFilters(); cy.getBySel(FLEET_AGENT_LIST_PAGE.STATUS_FILTER).click(); diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_status_filter.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_status_filter.tsx index 3f1790ec11979..1c4dedab5ee72 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_status_filter.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_status_filter.tsx @@ -39,6 +39,12 @@ const statusFilters = [ defaultMessage: 'Unhealthy', }), }, + { + status: 'orphaned', + label: i18n.translate('xpack.fleet.agentList.statusOrphanedFilterText', { + defaultMessage: 'Orphaned', + }), + }, { status: 'updating', label: i18n.translate('xpack.fleet.agentList.statusUpdatingFilterText', { @@ -63,6 +69,12 @@ const statusFilters = [ defaultMessage: 'Unenrolled', }), }, + { + status: 'uninstalled', + label: i18n.translate('xpack.fleet.agentList.statusUninstalledFilterText', { + defaultMessage: 'Uninstalled', + }), + }, ]; const LeftpaddedNotificationBadge = styled(EuiNotificationBadge)` diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx index 27cb000bdb154..f93fdcc59e7ad 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx @@ -121,7 +121,13 @@ describe('useFetchAgentsData', () => { const { result } = renderer.renderHook(() => useFetchAgentsData()); await waitFor(() => new Promise((resolve) => resolve(null))); - expect(result?.current.selectedStatus).toEqual(['healthy', 'unhealthy', 'updating', 'offline']); + expect(result?.current.selectedStatus).toEqual([ + 'healthy', + 'unhealthy', + 'orphaned', + 'updating', + 'offline', + ]); expect(result?.current.allAgentPolicies).toEqual([ { id: 'agent-policy-1', @@ -144,7 +150,7 @@ describe('useFetchAgentsData', () => { }, }); expect(result?.current.kuery).toEqual( - 'status:online or (status:error or status:degraded) or (status:updating or status:unenrolling or status:enrolling) or status:offline' + 'status:online or (status:error or status:degraded) or status:orphaned or (status:updating or status:unenrolling or status:enrolling) or status:offline' ); expect(result?.current.currentRequestRef).toEqual({ current: 2 }); expect(result?.current.pagination).toEqual({ currentPage: 1, pageSize: 5 }); diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx index 7c2ef37dcdb1f..0dd9291eccf94 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx @@ -123,6 +123,7 @@ export function useFetchAgentsData() { const [selectedStatus, setSelectedStatus] = useState([ 'healthy', 'unhealthy', + 'orphaned', 'updating', 'offline', ...(urlHasInactive ? ['inactive'] : []), @@ -235,7 +236,6 @@ export function useFetchAgentsData() { perPage: MAX_AGENT_ACTIONS, }), ]); - // Return if a newer request has been triggered if (currentRequestRef.current !== currentRequest) { return; @@ -263,6 +263,7 @@ export function useFetchAgentsData() { } const statusSummary = agentsResponse.data.statusSummary; + if (!statusSummary) { throw new Error('Invalid GET /agents response - no status summary'); } diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/get_kuery.ts b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/get_kuery.ts index 13f158dfe676f..9c620841e7161 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/get_kuery.ts +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/get_kuery.ts @@ -69,13 +69,17 @@ export const getKuery = ({ return AgentStatusKueryHelper.buildKueryForInactiveAgents(); case 'unenrolled': return AgentStatusKueryHelper.buildKueryForUnenrolledAgents(); + case 'orphaned': + return AgentStatusKueryHelper.buildKueryForOrphanedAgents(); + case 'uninstalled': + return AgentStatusKueryHelper.buildKueryForUninstalledAgents(); } return undefined; }) + .filter((statusKuery) => statusKuery !== undefined) .join(' or '); - if (kueryBuilder) { kueryBuilder = `(${kueryBuilder}) and (${kueryStatus})`; } else { diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index c7b71af5f97b6..df7cefdce0b2e 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -75,6 +75,25 @@ function getStatusComponent({ /> ); + case 'uninstalled': + return ( + + + + ); + case 'orphaned': + return ( + + + + ); + case 'unenrolling': case 'enrolling': case 'updating': @@ -185,15 +204,24 @@ export const AgentHealth: React.FunctionComponent = ({ > {isStuckInUpdating(agent) && !fromDetails ? (
- {getStatusComponent({ status: agent.status, ...restOfProps })} + {getStatusComponent({ + status: agent.status, + ...restOfProps, + })}  
) : ( <> - {getStatusComponent({ status: agent.status, ...restOfProps })} + {getStatusComponent({ + status: agent.status, + ...restOfProps, + })} {previousToOfflineStatus - ? getStatusComponent({ status: previousToOfflineStatus, ...restOfProps }) + ? getStatusComponent({ + status: previousToOfflineStatus, + ...restOfProps, + }) : null} )} diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx index 6b12331d7034c..2c61ab10ca78f 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx @@ -14,10 +14,12 @@ import type { SimplifiedAgentStatus } from '../../../types'; export const AGENT_STATUSES: SimplifiedAgentStatus[] = [ 'healthy', 'unhealthy', + 'orphaned', 'updating', 'offline', 'inactive', 'unenrolled', + 'uninstalled', ]; export function getColorForAgentStatus( @@ -33,10 +35,14 @@ export function getColorForAgentStatus( return euiTheme.colors.darkShade; case 'unhealthy': return euiTheme.colors.backgroundFilledWarning; + case 'orphaned': + return euiTheme.colors.backgroundFilledWarning; case 'updating': return euiTheme.colors.backgroundFilledPrimary; case 'unenrolled': return euiTheme.colors.backgroundBaseDisabled; + case 'uninstalled': + return euiTheme.colors.lightShade; default: throw new Error(`Unsupported Agent status ${agentStatus}`); } @@ -52,6 +58,10 @@ export function getLabelForAgentStatus(agentStatus: SimplifiedAgentStatus): stri return i18n.translate('xpack.fleet.agentStatus.offlineLabel', { defaultMessage: 'Offline', }); + case 'uninstalled': + return i18n.translate('xpack.fleet.agentStatus.uninstalledLabel', { + defaultMessage: 'Uninstalled', + }); case 'inactive': return i18n.translate('xpack.fleet.agentStatus.inactiveLabel', { defaultMessage: 'Inactive', @@ -64,6 +74,10 @@ export function getLabelForAgentStatus(agentStatus: SimplifiedAgentStatus): stri return i18n.translate('xpack.fleet.agentStatus.unhealthyLabel', { defaultMessage: 'Unhealthy', }); + case 'orphaned': + return i18n.translate('xpack.fleet.agentStatus.orphanedLabel', { + defaultMessage: 'Orphaned', + }); case 'updating': return i18n.translate('xpack.fleet.agentStatus.updatingLabel', { defaultMessage: 'Updating', diff --git a/x-pack/platform/plugins/shared/fleet/server/routes/agent/index.test.ts b/x-pack/platform/plugins/shared/fleet/server/routes/agent/index.test.ts index 34f66da425da3..beec337f76bf0 100644 --- a/x-pack/platform/plugins/shared/fleet/server/routes/agent/index.test.ts +++ b/x-pack/platform/plugins/shared/fleet/server/routes/agent/index.test.ts @@ -235,6 +235,8 @@ describe('schema validation', () => { degraded: 1, unenrolling: 1, enrolling: 1, + orphaned: 1, + uninstalled: 1, }, }; (getAgentsHandler as jest.Mock).mockImplementation((ctx, request, res) => { diff --git a/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.test.ts b/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.test.ts index 2c54d7d50c57c..983c0620fdc10 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.test.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.test.ts @@ -21,7 +21,7 @@ describe('buildStatusRuntimeField', () => { "status": Object { "script": Object { "lang": "painless", - "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", + "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'uninstall'){emit('uninstalled');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'orphaned'){emit('orphaned');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", }, "type": "keyword", }, @@ -36,7 +36,7 @@ describe('buildStatusRuntimeField', () => { "status": Object { "script": Object { "lang": "painless", - "source": " long lastCheckinMillis = doc['my.prefix.last_checkin'].size() > 0 ? doc['my.prefix.last_checkin'].value.toInstant().toEpochMilli() : ( doc['my.prefix.enrolled_at'].size() > 0 ? doc['my.prefix.enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['my.prefix.active'].size() > 0 && doc['my.prefix.active'].value == false) { emit('unenrolled'); } else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['my.prefix.policy_revision_idx'].size() == 0 || ( doc['my.prefix.upgrade_started_at'].size() > 0 && doc['my.prefix.upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['my.prefix.last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['my.prefix.unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['my.prefix.last_checkin_status'].size() > 0 && doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['my.prefix.last_checkin_status'].size() > 0 && doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", + "source": " long lastCheckinMillis = doc['my.prefix.last_checkin'].size() > 0 ? doc['my.prefix.last_checkin'].value.toInstant().toEpochMilli() : ( doc['my.prefix.enrolled_at'].size() > 0 ? doc['my.prefix.enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['my.prefix.active'].size() > 0 && doc['my.prefix.active'].value == false) { emit('unenrolled'); } else if (doc.containsKey('audit_unenrolled_reason') && doc['my.prefix.audit_unenrolled_reason'].size() > 0 && doc['my.prefix.audit_unenrolled_reason'].value == 'uninstall'){emit('uninstalled');} else if (doc.containsKey('audit_unenrolled_reason') && doc['my.prefix.audit_unenrolled_reason'].size() > 0 && doc['my.prefix.audit_unenrolled_reason'].value == 'orphaned'){emit('orphaned');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['my.prefix.policy_revision_idx'].size() == 0 || ( doc['my.prefix.upgrade_started_at'].size() > 0 && doc['my.prefix.upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['my.prefix.last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['my.prefix.unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['my.prefix.last_checkin_status'].size() > 0 && doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['my.prefix.last_checkin_status'].size() > 0 && doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", }, "type": "keyword", }, @@ -56,7 +56,7 @@ describe('buildStatusRuntimeField', () => { "status": Object { "script": Object { "lang": "painless", - "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L) {emit('inactive');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", + "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L) {emit('inactive');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'uninstall'){emit('uninstalled');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'orphaned'){emit('orphaned');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", }, "type": "keyword", }, @@ -76,7 +76,7 @@ describe('buildStatusRuntimeField', () => { "status": Object { "script": Object { "lang": "painless", - "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1','policy-2'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L) {emit('inactive');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", + "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1','policy-2'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L) {emit('inactive');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'uninstall'){emit('uninstalled');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'orphaned'){emit('orphaned');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", }, "type": "keyword", }, @@ -100,7 +100,7 @@ describe('buildStatusRuntimeField', () => { "status": Object { "script": Object { "lang": "painless", - "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", + "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'uninstall'){emit('uninstalled');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'orphaned'){emit('orphaned');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", }, "type": "keyword", }, @@ -124,7 +124,7 @@ describe('buildStatusRuntimeField', () => { "status": Object { "script": Object { "lang": "painless", - "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1','policy-2'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L || ['policy-3'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567490123L) {emit('inactive');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", + "source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1','policy-2'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L || ['policy-3'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567490123L) {emit('inactive');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'uninstall'){emit('uninstalled');} else if (doc.containsKey('audit_unenrolled_reason') && doc['audit_unenrolled_reason'].size() > 0 && doc['audit_unenrolled_reason'].value == 'orphaned'){emit('orphaned');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }", }, "type": "keyword", }, diff --git a/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.ts b/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.ts index 12314cb30ac68..3aaa56816be12 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/agents/build_status_runtime_field.ts @@ -91,45 +91,52 @@ function _buildSource( }); return ` - long lastCheckinMillis = ${field('last_checkin')}.size() > 0 - ? ${field('last_checkin')}.value.toInstant().toEpochMilli() + long lastCheckinMillis = ${field('last_checkin')}.size() > 0 + ? ${field('last_checkin')}.value.toInstant().toEpochMilli() : ( - ${field('enrolled_at')}.size() > 0 - ? ${field('enrolled_at')}.value.toInstant().toEpochMilli() + ${field('enrolled_at')}.size() > 0 + ? ${field('enrolled_at')}.value.toInstant().toEpochMilli() : -1 ); if (${field('active')}.size() > 0 && ${field('active')}.value == false) { - emit('unenrolled'); - } ${agentIsInactiveCondition ? `else if (${agentIsInactiveCondition}) {emit('inactive');}` : ''} - else if ( - lastCheckinMillis > 0 - && lastCheckinMillis + emit('unenrolled'); + } + ${agentIsInactiveCondition ? `else if (${agentIsInactiveCondition}) {emit('inactive');}` : ''} + else if (doc.containsKey('audit_unenrolled_reason') && ${field( + 'audit_unenrolled_reason' + )}.size() > 0 && ${field('audit_unenrolled_reason')}.value == 'uninstall'){emit('uninstalled');} + else if (doc.containsKey('audit_unenrolled_reason') && ${field( + 'audit_unenrolled_reason' + )}.size() > 0 && ${field('audit_unenrolled_reason')}.value == 'orphaned'){emit('orphaned');} + else if ( + lastCheckinMillis > 0 + && lastCheckinMillis < ${now - MS_BEFORE_OFFLINE}L - ) { - emit('offline'); + ) { + emit('offline'); } else if ( ${field('policy_revision_idx')}.size() == 0 || ( ${field('upgrade_started_at')}.size() > 0 && ${field('upgraded_at')}.size() == 0 ) - ) { - emit('updating'); + ) { + emit('updating'); } else if (${field('last_checkin')}.size() == 0) { - emit('enrolling'); + emit('enrolling'); } else if (${field('unenrollment_started_at')}.size() > 0) { - emit('unenrolling'); + emit('unenrolling'); } else if ( ${field('last_checkin_status')}.size() > 0 && ${field('last_checkin_status')}.value.toLowerCase() == 'error' - ) { + ) { emit('error'); } else if ( ${field('last_checkin_status')}.size() > 0 && ${field('last_checkin_status')}.value.toLowerCase() == 'degraded' - ) { + ) { emit('degraded'); - } else { - emit('online'); + } else { + emit('online'); }`.replace(/\s{2,}/g, ' '); // replace newlines and double spaces to save characters } diff --git a/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.test.ts b/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.test.ts index 6ccf5653bfc8f..63b4bddab292e 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.test.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.test.ts @@ -324,8 +324,10 @@ describe('Agents CRUD test', () => { inactive: 0, offline: 0, online: 0, + orphaned: 0, unenrolled: 0, unenrolling: 0, + uninstalled: 0, updating: 1, }, total: 1, @@ -357,8 +359,10 @@ describe('Agents CRUD test', () => { error: 0, inactive: 0, offline: 0, + orphaned: 0, online: 0, unenrolled: 0, + uninstalled: 0, unenrolling: 0, updating: 1, }, diff --git a/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.ts b/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.ts index bdc90ead11143..caf7ae42cbe4a 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/agents/crud.ts @@ -266,6 +266,8 @@ export async function getAgentsByKuery( degraded: 0, enrolling: 0, unenrolling: 0, + orphaned: 0, + uninstalled: 0, }; const queryAgents = async (from: number, size: number) => { diff --git a/x-pack/platform/plugins/shared/fleet/server/services/agents/status.ts b/x-pack/platform/plugins/shared/fleet/server/services/agents/status.ts index 384c7b1252554..175358019bfd1 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/agents/status.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/agents/status.ts @@ -111,6 +111,8 @@ export async function getAgentStatusForAgentPolicy( error: 0, inactive: 0, offline: 0, + uninstalled: 0, + orphaned: 0, updating: 0, unenrolled: 0, degraded: 0, diff --git a/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts b/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts index 981bcc7e18959..3be93963d0a60 100644 --- a/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts @@ -80,6 +80,8 @@ export const AgentStatusSchema = schema.oneOf([ schema.literal('unenrolled'), schema.literal('updating'), schema.literal('degraded'), + schema.literal('uninstalled'), + schema.literal('orphaned'), ]); export const AgentResponseSchema = schema.object({ @@ -519,6 +521,8 @@ export const GetAgentStatusResponseSchema = schema.object({ online: schema.number(), error: schema.number(), offline: schema.number(), + uninstalled: schema.maybe(schema.number()), + orphaned: schema.maybe(schema.number()), other: schema.number(), updating: schema.number(), inactive: schema.number(), diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index bcbdcf16b296e..505a703604801 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -209,9 +209,11 @@ export default function ({ getService }: FtrProviderContext) { inactive: 0, offline: 4, online: 0, + orphaned: 0, unenrolled: 0, unenrolling: 0, updating: 0, + uninstalled: 0, }); }); @@ -257,9 +259,11 @@ export default function ({ getService }: FtrProviderContext) { inactive: 0, offline: 0, online: 2, + orphaned: 0, unenrolled: 0, unenrolling: 0, updating: 0, + uninstalled: 0, }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/status.ts b/x-pack/test/fleet_api_integration/apis/agents/status.ts index 4b56601078da2..9805ca84b9718 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/status.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/status.ts @@ -206,6 +206,40 @@ export default function ({ getService }: FtrProviderContext) { enrolled_at: new Date().toISOString(), }, }); + // 1 uninstalled agent + await es.create({ + id: 'agent12', + refresh: 'wait_for', + index: AGENTS_INDEX, + document: { + active: true, + access_api_key_id: 'api-key-4', + policy_id: 'policy-inactivity-timeout', + type: 'PERMANENT', + policy_revision_idx: 1, + local_metadata: { host: { hostname: 'host6' } }, + user_provided_metadata: {}, + enrolled_at: new Date().toISOString(), + audit_unenrolled_reason: 'uninstall', + }, + }); + // 1 orphaned agent + await es.create({ + id: 'agent13', + refresh: 'wait_for', + index: AGENTS_INDEX, + document: { + active: true, + access_api_key_id: 'api-key-4', + policy_id: 'policy-inactivity-timeout', + type: 'PERMANENT', + policy_revision_idx: 1, + local_metadata: { host: { hostname: 'host6' } }, + user_provided_metadata: {}, + enrolled_at: new Date().toISOString(), + audit_unenrolled_reason: 'orphaned', + }, + }); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents'); @@ -218,13 +252,15 @@ export default function ({ getService }: FtrProviderContext) { events: 0, other: 0, online: 2, - active: 8, - all: 11, + active: 10, + all: 13, error: 2, offline: 1, updating: 3, inactive: 2, unenrolled: 1, + orphaned: 1, + uninstalled: 1, }, }); }); @@ -292,13 +328,15 @@ export default function ({ getService }: FtrProviderContext) { events: 0, other: 0, online: 3, - active: 10, - all: 11, + active: 12, + all: 13, error: 2, offline: 1, updating: 4, inactive: 0, unenrolled: 1, + orphaned: 1, + uninstalled: 1, }, }); });