diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index fa3b6b56a18b94..9cc1cede549b60 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -31,8 +31,8 @@ import { RedirectAppLinks } from '../../../../../../../../../../../src/plugins/k import type { TimeRange } from '../../../../../../../../../../../src/plugins/data/public'; import { esKuery } from '../../../../../../../../../../../src/plugins/data/public'; import { LogStream } from '../../../../../../../../../infra/public'; -import type { Agent } from '../../../../../types'; -import { useStartServices } from '../../../../../hooks'; +import type { Agent, AgentPolicy } from '../../../../../types'; +import { useLink, useStartServices } from '../../../../../hooks'; import { DEFAULT_DATE_RANGE } from './constants'; import { DatasetFilter } from './filter_dataset'; @@ -51,6 +51,7 @@ const DatePickerFlexItem = styled(EuiFlexItem)` export interface AgentLogsProps { agent: Agent; + agentPolicy?: AgentPolicy; state: AgentLogsState; } @@ -64,251 +65,298 @@ export interface AgentLogsState { export const AgentLogsUrlStateHelper = createStateContainerReactHelpers(); -export const AgentLogsUI: React.FunctionComponent = memo(({ agent, state }) => { - const { data, application, http } = useStartServices(); - const { update: updateState } = AgentLogsUrlStateHelper.useTransitions(); +const AgentPolicyLogsNotEnabledCallout: React.FunctionComponent<{ agentPolicy: AgentPolicy }> = ({ + agentPolicy, +}) => { + const { getHref } = useLink(); - // Util to convert date expressions (returned by datepicker) to timestamps (used by LogStream) - const getDateRangeTimestamps = useCallback( - (timeRange: TimeRange) => { - const { min, max } = data.query.timefilter.timefilter.calculateBounds(timeRange); - return min && max - ? { - start: min.valueOf(), - end: max.valueOf(), - } - : undefined; - }, - [data.query.timefilter.timefilter] + return ( + + + } + > + + + + ), + }} + /> + + ); +}; - const tryUpdateDateRange = useCallback( - (timeRange: TimeRange) => { - const timestamps = getDateRangeTimestamps(timeRange); - if (timestamps) { - updateState({ - start: timeRange.from, - end: timeRange.to, - }); - } - }, - [getDateRangeTimestamps, updateState] - ); +export const AgentLogsUI: React.FunctionComponent = memo( + ({ agent, agentPolicy, state }) => { + const { data, application, http } = useStartServices(); + const { update: updateState } = AgentLogsUrlStateHelper.useTransitions(); + + // Util to convert date expressions (returned by datepicker) to timestamps (used by LogStream) + const getDateRangeTimestamps = useCallback( + (timeRange: TimeRange) => { + const { min, max } = data.query.timefilter.timefilter.calculateBounds(timeRange); + return min && max + ? { + start: min.valueOf(), + end: max.valueOf(), + } + : undefined; + }, + [data.query.timefilter.timefilter] + ); + + const tryUpdateDateRange = useCallback( + (timeRange: TimeRange) => { + const timestamps = getDateRangeTimestamps(timeRange); + if (timestamps) { + updateState({ + start: timeRange.from, + end: timeRange.to, + }); + } + }, + [getDateRangeTimestamps, updateState] + ); - const [dateRangeTimestamps, setDateRangeTimestamps] = useState<{ start: number; end: number }>( - getDateRangeTimestamps({ - from: state.start, - to: state.end, - }) || + const [dateRangeTimestamps, setDateRangeTimestamps] = useState<{ start: number; end: number }>( getDateRangeTimestamps({ - from: DEFAULT_DATE_RANGE.start, - to: DEFAULT_DATE_RANGE.end, - })! - ); + from: state.start, + to: state.end, + }) || + getDateRangeTimestamps({ + from: DEFAULT_DATE_RANGE.start, + to: DEFAULT_DATE_RANGE.end, + })! + ); - // Attempts to parse for timestamps when start/end date expressions change - // If invalid date expressions, set expressions back to default - // Otherwise set the new timestamps - useEffect(() => { - const timestampsFromDateRange = getDateRangeTimestamps({ - from: state.start, - to: state.end, - }); - if (!timestampsFromDateRange) { - tryUpdateDateRange({ - from: DEFAULT_DATE_RANGE.start, - to: DEFAULT_DATE_RANGE.end, + // Attempts to parse for timestamps when start/end date expressions change + // If invalid date expressions, set expressions back to default + // Otherwise set the new timestamps + useEffect(() => { + const timestampsFromDateRange = getDateRangeTimestamps({ + from: state.start, + to: state.end, }); - } else { - setDateRangeTimestamps(timestampsFromDateRange); - } - }, [state.start, state.end, getDateRangeTimestamps, tryUpdateDateRange]); + if (!timestampsFromDateRange) { + tryUpdateDateRange({ + from: DEFAULT_DATE_RANGE.start, + to: DEFAULT_DATE_RANGE.end, + }); + } else { + setDateRangeTimestamps(timestampsFromDateRange); + } + }, [state.start, state.end, getDateRangeTimestamps, tryUpdateDateRange]); - // Query validation helper - const isQueryValid = useCallback((testQuery: string) => { - try { - esKuery.fromKueryExpression(testQuery); - return true; - } catch (err) { - return false; - } - }, []); + // Query validation helper + const isQueryValid = useCallback((testQuery: string) => { + try { + esKuery.fromKueryExpression(testQuery); + return true; + } catch (err) { + return false; + } + }, []); - // User query state - const [draftQuery, setDraftQuery] = useState(state.query); - const [isDraftQueryValid, setIsDraftQueryValid] = useState(isQueryValid(state.query)); - const onUpdateDraftQuery = useCallback( - (newDraftQuery: string, runQuery?: boolean) => { - setDraftQuery(newDraftQuery); - if (isQueryValid(newDraftQuery)) { - setIsDraftQueryValid(true); - if (runQuery) { - updateState({ query: newDraftQuery }); + // User query state + const [draftQuery, setDraftQuery] = useState(state.query); + const [isDraftQueryValid, setIsDraftQueryValid] = useState(isQueryValid(state.query)); + const onUpdateDraftQuery = useCallback( + (newDraftQuery: string, runQuery?: boolean) => { + setDraftQuery(newDraftQuery); + if (isQueryValid(newDraftQuery)) { + setIsDraftQueryValid(true); + if (runQuery) { + updateState({ query: newDraftQuery }); + } + } else { + setIsDraftQueryValid(false); } - } else { - setIsDraftQueryValid(false); - } - }, - [isQueryValid, updateState] - ); + }, + [isQueryValid, updateState] + ); - // Build final log stream query from agent id, datasets, log levels, and user input - const logStreamQuery = useMemo( - () => - buildQuery({ - agentId: agent.id, - datasets: state.datasets, - logLevels: state.logLevels, - userQuery: state.query, - }), - [agent.id, state.datasets, state.logLevels, state.query] - ); + // Build final log stream query from agent id, datasets, log levels, and user input + const logStreamQuery = useMemo( + () => + buildQuery({ + agentId: agent.id, + datasets: state.datasets, + logLevels: state.logLevels, + userQuery: state.query, + }), + [agent.id, state.datasets, state.logLevels, state.query] + ); - // Generate URL to pass page state to Logs UI - const viewInLogsUrl = useMemo( - () => - http.basePath.prepend( - url.format({ - pathname: '/app/logs/stream', - search: stringify({ - logPosition: encode({ - start: state.start, - end: state.end, - streamLive: false, + // Generate URL to pass page state to Logs UI + const viewInLogsUrl = useMemo( + () => + http.basePath.prepend( + url.format({ + pathname: '/app/logs/stream', + search: stringify({ + logPosition: encode({ + start: state.start, + end: state.end, + streamLive: false, + }), + logFilter: encode({ + expression: logStreamQuery, + kind: 'kuery', + }), }), - logFilter: encode({ - expression: logStreamQuery, - kind: 'kuery', - }), - }), - }) - ), - [http.basePath, state.start, state.end, logStreamQuery] - ); - - const agentVersion = agent.local_metadata?.elastic?.agent?.version; - const isLogFeatureAvailable = useMemo(() => { - if (!agentVersion) { - return false; - } - const agentVersionWithPrerelease = semverCoerce(agentVersion)?.version; - if (!agentVersionWithPrerelease) { - return false; - } - return semverGte(agentVersionWithPrerelease, '7.11.0'); - }, [agentVersion]); + }) + ), + [http.basePath, state.start, state.end, logStreamQuery] + ); - // Set absolute height on logs component (needed to render correctly in Safari) - // based on available height, or 600px, whichever is greater - const [logsPanelRef, { height: measuredlogPanelHeight }] = useMeasure(); - const logPanelHeight = useMemo(() => Math.max(measuredlogPanelHeight, 600), [ - measuredlogPanelHeight, - ]); + const agentVersion = agent.local_metadata?.elastic?.agent?.version; + const isLogFeatureAvailable = useMemo(() => { + if (!agentVersion) { + return false; + } + const agentVersionWithPrerelease = semverCoerce(agentVersion)?.version; + if (!agentVersionWithPrerelease) { + return false; + } + return semverGte(agentVersionWithPrerelease, '7.11.0'); + }, [agentVersion]); - if (!isLogFeatureAvailable) { - return ( - - - - ), - }} - /> - } - /> - ); - } + // Set absolute height on logs component (needed to render correctly in Safari) + // based on available height, or 600px, whichever is greater + const [logsPanelRef, { height: measuredlogPanelHeight }] = useMeasure(); + const logPanelHeight = useMemo(() => Math.max(measuredlogPanelHeight, 600), [ + measuredlogPanelHeight, + ]); - return ( - - - - - + + + ), + }} /> - - - - { - const currentDatasets = [...state.datasets]; - const datasetPosition = currentDatasets.indexOf(dataset); - if (datasetPosition >= 0) { - currentDatasets.splice(datasetPosition, 1); - updateState({ datasets: currentDatasets }); - } else { - updateState({ datasets: [...state.datasets, dataset] }); - } - }} + } + /> + ); + } + + return ( + + {agentPolicy && !agentPolicy.monitoring_enabled?.includes('logs') && ( + + )} + + + + - { - const currentLevels = [...state.logLevels]; - const levelPosition = currentLevels.indexOf(level); - if (levelPosition >= 0) { - currentLevels.splice(levelPosition, 1); - updateState({ logLevels: currentLevels }); - } else { - updateState({ logLevels: [...state.logLevels, level] }); - } + + + + { + const currentDatasets = [...state.datasets]; + const datasetPosition = currentDatasets.indexOf(dataset); + if (datasetPosition >= 0) { + currentDatasets.splice(datasetPosition, 1); + updateState({ datasets: currentDatasets }); + } else { + updateState({ datasets: [...state.datasets, dataset] }); + } + }} + /> + { + const currentLevels = [...state.logLevels]; + const levelPosition = currentLevels.indexOf(level); + if (levelPosition >= 0) { + currentLevels.splice(levelPosition, 1); + updateState({ logLevels: currentLevels }); + } else { + updateState({ logLevels: [...state.logLevels, level] }); + } + }} + /> + + + + { + tryUpdateDateRange({ + from: start, + to: end, + }); }} /> - - - - { - tryUpdateDateRange({ - from: start, - to: end, - }); - }} + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - ); -}); + + + + + + + ); + } +); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx index ff31ffef039251..0e2c01f095f3e9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx @@ -22,57 +22,57 @@ import { DEFAULT_LOGS_STATE, STATE_STORAGE_KEY } from './constants'; import type { AgentLogsProps, AgentLogsState } from './agent_logs'; import { AgentLogsUI, AgentLogsUrlStateHelper } from './agent_logs'; -export const AgentLogs: React.FunctionComponent> = memo( - ({ agent }) => { - const stateContainer = useMemo( - () => - createStateContainer< - AgentLogsState, - { - update: PureTransition]>; - } - >( - { - ...DEFAULT_LOGS_STATE, - ...getStateFromKbnUrl(STATE_STORAGE_KEY, window.location.href), - }, - { - update: (state) => (updatedState) => ({ ...state, ...updatedState }), - } - ), - [] - ); +export const AgentLogs: React.FunctionComponent< + Pick +> = memo(({ agent, agentPolicy }) => { + const stateContainer = useMemo( + () => + createStateContainer< + AgentLogsState, + { + update: PureTransition]>; + } + >( + { + ...DEFAULT_LOGS_STATE, + ...getStateFromKbnUrl(STATE_STORAGE_KEY, window.location.href), + }, + { + update: (state) => (updatedState) => ({ ...state, ...updatedState }), + } + ), + [] + ); - const AgentLogsConnected = useMemo( - () => - AgentLogsUrlStateHelper.connect((state) => ({ - state: state || DEFAULT_LOGS_STATE, - }))(AgentLogsUI), - [] - ); + const AgentLogsConnected = useMemo( + () => + AgentLogsUrlStateHelper.connect((state) => ({ + state: state || DEFAULT_LOGS_STATE, + }))(AgentLogsUI), + [] + ); - const [isSyncReady, setIsSyncReady] = useState(false); + const [isSyncReady, setIsSyncReady] = useState(false); - useEffect(() => { - const stateStorage = createKbnUrlStateStorage(); - const { start, stop } = syncState({ - storageKey: STATE_STORAGE_KEY, - stateContainer: stateContainer as INullableBaseStateContainer, - stateStorage, - }); - start(); - setIsSyncReady(true); + useEffect(() => { + const stateStorage = createKbnUrlStateStorage(); + const { start, stop } = syncState({ + storageKey: STATE_STORAGE_KEY, + stateContainer: stateContainer as INullableBaseStateContainer, + stateStorage, + }); + start(); + setIsSyncReady(true); - return () => { - stop(); - stateContainer.set(DEFAULT_LOGS_STATE); - }; - }, [stateContainer]); + return () => { + stop(); + stateContainer.set(DEFAULT_LOGS_STATE); + }; + }, [stateContainer]); - return ( - - {isSyncReady ? : null} - - ); - } -); + return ( + + {isSyncReady ? : null} + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index ce213808563e65..559cefc5fc720a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -306,7 +306,7 @@ const AgentDetailsPageContent: React.FunctionComponent<{ { - return ; + return ; }} />