diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
index 06096b1c3de5b..a3d9e8884d7d3 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
@@ -42,7 +42,13 @@ import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_a
import { AgentBasedPackagePoliciesTable } from './components/agent_based_table';
import { AgentlessPackagePoliciesTable } from './components/agentless_table';
-export const PackagePoliciesPage = ({ packageInfo }: { packageInfo: PackageInfo }) => {
+export const PackagePoliciesPage = ({
+ packageInfo,
+ embedded,
+}: {
+ packageInfo: PackageInfo;
+ embedded?: boolean;
+}) => {
const { name, version } = packageInfo;
const { search } = useLocation();
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
@@ -154,7 +160,11 @@ export const PackagePoliciesPage = ({ packageInfo }: { packageInfo: PackageInfo
// 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 `addAgentToPolicyIdFromParams` otherwise right after installing a new integration the flyout won't open
- if (packageInstallStatus.status !== InstallStatus.installed && !addAgentToPolicyIdFromParams) {
+ if (
+ packageInstallStatus &&
+ packageInstallStatus.status !== InstallStatus.installed &&
+ !addAgentToPolicyIdFromParams
+ ) {
return (
);
@@ -170,7 +180,7 @@ export const PackagePoliciesPage = ({ packageInfo }: { packageInfo: PackageInfo
}}
>
-
+ {embedded ? null : }
{!canHaveAgentlessPolicies ? (
-
+
-
+
- {agentlessData?.total ?? 0}
+ {agentlessData?.total ?? 0}
@@ -244,17 +254,17 @@ export const PackagePoliciesPage = ({ packageInfo }: { packageInfo: PackageInfo
>
-
+
-
+
- {agentBasedData?.total ?? 0}
+ {agentBasedData?.total ?? 0}
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/installed_integrations_table.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/installed_integrations_table.tsx
index 741c6bd275c2e..ad4e000f06e53 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/installed_integrations_table.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/installed_integrations_table.tsx
@@ -22,6 +22,7 @@ import { TableIcon } from '../../../../../../../components/package_icon';
import type { PackageListItem } from '../../../../../../../../common';
import { type UrlPagination, useLink, useAuthz } from '../../../../../../../hooks';
import type { InstalledPackageUIPackageListItem } from '../types';
+import { useViewPolicies } from '../hooks/use_url_filters';
import { InstallationVersionStatus } from './installation_version_status';
import { DisabledWrapperTooltip } from './disabled_wrapper_tooltip';
@@ -39,6 +40,7 @@ export const InstalledIntegrationsTable: React.FunctionComponent<{
const authz = useAuthz();
const { getHref } = useLink();
const { selectedItems, setSelectedItems } = selection;
+ const { addViewPolicies } = useViewPolicies();
const { setPagination } = pagination;
const handleTablePagination = React.useCallback(
@@ -148,7 +150,7 @@ export const InstalledIntegrationsTable: React.FunctionComponent<{
}
disabled={isDisabled}
>
- {}} disabled={isDisabled}>
+ addViewPolicies(item.name)} disabled={isDisabled}>
= ({ installedPackage }) => {
+ const { addViewPolicies } = useViewPolicies();
+ const paddingStyles = useEuiPaddingCSS();
+ const cssStyles = [paddingStyles.s];
+
+ const title = (
+
+
+
+
+
+ {installedPackage.title}
+
+
+ );
+
+ const content = (
+
+ );
+
+ return addViewPolicies('')} />;
+};
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/resizable_panel.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/resizable_panel.tsx
new file mode 100644
index 0000000000000..a57256f8f713f
--- /dev/null
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/components/resizable_panel.tsx
@@ -0,0 +1,226 @@
+/*
+ * 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, { useState, useCallback, useRef, useEffect } from 'react';
+import {
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPortal,
+ useEuiPaddingCSS,
+ useEuiTheme,
+} from '@elastic/eui';
+import { EuiResizableButton, EuiPanel, keys } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { i18n } from '@kbn/i18n';
+
+const getMouseOrTouchY = (
+ e: TouchEvent | MouseEvent | React.MouseEvent | React.TouchEvent
+): number => {
+ const x = (e as TouchEvent).targetTouches
+ ? (e as TouchEvent).targetTouches[0].pageY
+ : (e as MouseEvent).pageY;
+ return -x;
+};
+
+export const ResizablePanelComponent: React.FunctionComponent<{
+ topBar: React.ReactNode;
+ children: React.ReactNode;
+ isCollapsed: boolean;
+}> = ({ children, isCollapsed, topBar }) => {
+ const euiTheme = useEuiTheme();
+ const [panelHeight, setPanelHeight] = useState(300);
+ const initialPanelHeight = useRef(panelHeight);
+ const initialMouseY = useRef(0);
+
+ const normalizeHeight = useCallback(
+ (height: number) => {
+ const marginTop = parseInt(euiTheme.euiTheme.size.xxxxl, 10);
+ // Be sure to not go over top bar
+ return Math.min(Math.max(height, 0), window.innerHeight - marginTop * 3);
+ },
+ [euiTheme.euiTheme.size.xxxxl]
+ );
+
+ useEffect(() => {
+ function onResize() {
+ const normalizedHeight = normalizeHeight(panelHeight);
+ if (normalizedHeight !== panelHeight) {
+ setPanelHeight(normalizedHeight);
+ }
+ }
+ window.addEventListener('resize', onResize);
+ return () => {
+ window.removeEventListener('resize', onResize);
+ };
+ }, [panelHeight, normalizeHeight]);
+
+ const onMouseMove = useCallback(
+ (e: MouseEvent | TouchEvent) => {
+ const mouseOffset = getMouseOrTouchY(e) - initialMouseY.current;
+ const changedPanelHeight = initialPanelHeight.current + mouseOffset;
+
+ setPanelHeight(normalizeHeight(changedPanelHeight));
+ },
+ [normalizeHeight]
+ );
+
+ const onMouseUp = useCallback(() => {
+ initialMouseY.current = 0;
+
+ window.removeEventListener('mousemove', onMouseMove);
+ window.removeEventListener('mouseup', onMouseUp);
+ window.removeEventListener('touchmove', onMouseMove);
+ window.removeEventListener('touchend', onMouseUp);
+ }, [onMouseMove]);
+
+ const onMouseDown = useCallback(
+ (e: React.MouseEvent | React.TouchEvent) => {
+ initialMouseY.current = getMouseOrTouchY(e);
+ initialPanelHeight.current = panelHeight;
+
+ // Window event listeners instead of React events are used
+ // in case the user's mouse leaves the component
+ window.addEventListener('mousemove', onMouseMove);
+ window.addEventListener('mouseup', onMouseUp);
+ window.addEventListener('touchmove', onMouseMove);
+ window.addEventListener('touchend', onMouseUp);
+ },
+ [panelHeight, onMouseMove, onMouseUp]
+ );
+
+ const onKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ const KEYBOARD_OFFSET = 10;
+
+ switch (e.key) {
+ case keys.ARROW_UP:
+ e.preventDefault(); // Safari+VO will screen reader navigate off the button otherwise
+ setPanelHeight((currentPanelHeight) =>
+ normalizeHeight(currentPanelHeight + KEYBOARD_OFFSET)
+ );
+ break;
+ case keys.ARROW_DOWN:
+ e.preventDefault(); // Safari+VO will screen reader navigate off the button otherwise
+ setPanelHeight((currentPanelHeight) =>
+ normalizeHeight(currentPanelHeight - KEYBOARD_OFFSET)
+ );
+ }
+ },
+ [normalizeHeight]
+ );
+
+ return (
+
+ {topBar}
+
+
+ {children}
+
+
+ );
+};
+
+export const ResizablePanel: React.FunctionComponent<{
+ title: React.ReactNode;
+ content: React.ReactNode;
+ onClose: () => void;
+}> = ({ title, content, onClose }) => {
+ const euiTheme = useEuiTheme();
+
+ const paddingStyles = useEuiPaddingCSS();
+ const cssStyles = [paddingStyles.m];
+
+ const toggleCollpase = useCallback(() => {
+ setIsCollapsed((current) => !current);
+ }, []);
+
+ const [isCollapsed, setIsCollapsed] = useState(false);
+
+ const topBar = (
+
+ {title}
+
+
+
+ {isCollapsed ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+
+ return (
+
+
+ {content}
+
+
+ );
+};
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/hooks/use_url_filters.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/hooks/use_url_filters.tsx
index 58087c5a2ca37..86b4ecac428ff 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/hooks/use_url_filters.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/hooks/use_url_filters.tsx
@@ -46,6 +46,39 @@ export function useAddUrlFilters() {
);
}
+export function useViewPolicies() {
+ const { toUrlParams, urlParams } = useUrlParams();
+ const history = useHistory();
+
+ const addViewPolicies = useCallback(
+ (packageName: string) => {
+ history.push({
+ search: toUrlParams(
+ {
+ ...omit(urlParams, 'viewPolicies'),
+ ...(packageName ? { viewPolicies: packageName } : {}),
+ },
+ {
+ skipEmptyString: true,
+ }
+ ),
+ });
+ },
+ [urlParams, toUrlParams, history]
+ );
+
+ const selectedPackageViewPolicies = useMemo(() => {
+ if (typeof urlParams.viewPolicies === 'string') {
+ return urlParams.viewPolicies;
+ }
+ }, [urlParams]);
+
+ return {
+ addViewPolicies,
+ selectedPackageViewPolicies,
+ };
+}
+
export function useUrlFilters(): InstalledIntegrationsFilter {
const { urlParams } = useUrlParams();
diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/index.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/index.tsx
index 7d48d0ec10222..0c86b75e72e2d 100644
--- a/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/index.tsx
+++ b/x-pack/platform/plugins/shared/fleet/public/applications/integrations/sections/epm/screens/installed_integrations/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { EuiSpacer } from '@elastic/eui';
import styled from '@emotion/styled';
@@ -14,10 +14,11 @@ import { useUrlPagination } from '../../../../../../hooks';
import { InstalledIntegrationsTable } from './components/installed_integrations_table';
import { useInstalledIntegrations } from './hooks/use_installed_integrations';
-import { useUrlFilters } from './hooks/use_url_filters';
+import { useUrlFilters, useViewPolicies } from './hooks/use_url_filters';
import { InstalledIntegrationsSearchBar } from './components/installed_integrations_search_bar';
import type { InstalledPackageUIPackageListItem } from './types';
import { BulkActionContextProvider, useBulkActions } from './hooks/use_bulk_actions';
+import { PackagePoliciesPanel } from './components/package_policies_panel';
const ContentWrapper = styled.div`
max-width: 1200px;
@@ -28,6 +29,7 @@ const ContentWrapper = styled.div`
const InstalledIntegrationsPageContent: React.FunctionComponent = () => {
// State management
const filters = useUrlFilters();
+ const { selectedPackageViewPolicies } = useViewPolicies();
const pagination = useUrlPagination();
const { upgradingIntegrations, uninstallingIntegrations } = useBulkActions();
const {
@@ -46,27 +48,40 @@ const InstalledIntegrationsPageContent: React.FunctionComponent = () => {
const [selectedItems, setSelectedItems] = useState([]);
+ const viewPoliciesSelectedItem = useMemo(
+ () =>
+ selectedPackageViewPolicies
+ ? installedPackages.find((item) => item.name === selectedPackageViewPolicies)
+ : null,
+ [selectedPackageViewPolicies, installedPackages]
+ );
+
if (isInitialLoading) {
return ;
}
return (
-
-
-
-
-
+ <>
+
+
+
+
+
+ {viewPoliciesSelectedItem ? (
+
+ ) : null}
+ >
);
};