diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index bfe6716b0d2c0..332bf2832f0ba 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -578,6 +578,7 @@ x-pack/plugins/serverless @elastic/appex-sharedux
x-pack/plugins/serverless_observability @elastic/appex-sharedux
packages/serverless/project_switcher @elastic/appex-sharedux
x-pack/plugins/serverless_search @elastic/appex-sharedux
+x-pack/plugins/serverless_security @elastic/appex-sharedux
packages/serverless/storybook/config @elastic/appex-sharedux
packages/serverless/types @elastic/appex-sharedux
test/plugin_functional/plugins/session_notifications @elastic/kibana-core
diff --git a/config/serverless.security.yml b/config/serverless.security.yml
index e906e82191409..f01186a6ceecf 100644
--- a/config/serverless.security.yml
+++ b/config/serverless.security.yml
@@ -1,2 +1,16 @@
+# Security Project config
+
+## Disable plugins
+enterpriseSearch.enabled: false
+xpack.apm.enabled: false
+xpack.observability.enabled: false
+xpack.uptime.enabled: false
+
+## Enable the Serverless Security plugin
+xpack.serverless.security.enabled: true
+
+## Set the home route
uiSettings.overrides.defaultRoute: /app/security/get_started
+
+## Set the dev project switcher current type
xpack.serverless.plugin.developer.projectSwitcher.currentType: 'security'
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 64b718e955f67..e218387d64341 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -722,6 +722,10 @@ Kibana.
|This plugin contains configuration and code used to create a Serverless Search project. It leverages universal configuration and other APIs in the serverless plugin to configure Kibana.
+|{kib-repo}blob/{branch}/x-pack/plugins/serverless_security/README.mdx[serverlessSecurity]
+|This plugin contains configuration and code used to create a Serverless Security project. It leverages universal configuration and other APIs in the serverless plugin to configure Kibana.
+
+
|{kib-repo}blob/{branch}/x-pack/plugins/session_view/README.md[sessionView]
|Session View is meant to provide a visualization into what is going on in a particular Linux environment where the agent is running. It looks likes a terminal emulator; however, it is a tool for introspecting process activity and understanding user and service behaviour in your Linux servers and infrastructure. It is a time-ordered series of process executions displayed in a tree over time.
diff --git a/package.json b/package.json
index 01793344399ed..35957c9e202b6 100644
--- a/package.json
+++ b/package.json
@@ -579,6 +579,7 @@
"@kbn/serverless-observability": "link:x-pack/plugins/serverless_observability",
"@kbn/serverless-project-switcher": "link:packages/serverless/project_switcher",
"@kbn/serverless-search": "link:x-pack/plugins/serverless_search",
+ "@kbn/serverless-security": "link:x-pack/plugins/serverless_security",
"@kbn/serverless-types": "link:packages/serverless/types",
"@kbn/session-notifications-plugin": "link:test/plugin_functional/plugins/session_notifications",
"@kbn/session-view-plugin": "link:x-pack/plugins/session_view",
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 85c8b6c888236..13cf535a36873 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -118,6 +118,7 @@ pageLoadAssetSize:
serverless: 16573
serverlessObservability: 16582
serverlessSearch: 17548
+ serverlessSecurity: 41807
sessionView: 77750
share: 71239
snapshotRestore: 79032
diff --git a/src/plugins/management/public/application.tsx b/src/plugins/management/public/application.tsx
index dc16faa4f6d04..8c4a8af510391 100644
--- a/src/plugins/management/public/application.tsx
+++ b/src/plugins/management/public/application.tsx
@@ -16,15 +16,6 @@ export const renderApp = async (
{ history, appBasePath, element, theme$ }: AppMountParameters,
dependencies: ManagementAppDependencies
) => {
- ReactDOM.render(
- ,
- element
- );
-
+ ReactDOM.render(, element);
return () => ReactDOM.unmountComponentAtNode(element);
};
diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx
index bc0b88e7dffcb..50ab45a867c98 100644
--- a/src/plugins/management/public/components/management_app/management_app.tsx
+++ b/src/plugins/management/public/components/management_app/management_app.tsx
@@ -8,12 +8,15 @@
import './management_app.scss';
import React, { useState, useEffect, useCallback } from 'react';
+import { BehaviorSubject } from 'rxjs';
+
import { I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public';
import { reactRouterNavigate, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaPageTemplate, KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
+import useObservable from 'react-use/lib/useObservable';
import {
ManagementSection,
MANAGEMENT_BREADCRUMB,
@@ -34,12 +37,14 @@ export interface ManagementAppDependencies {
sections: SectionsServiceStart;
kibanaVersion: string;
setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void;
+ isSidebarEnabled$: BehaviorSubject;
}
export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppProps) => {
- const { setBreadcrumbs } = dependencies;
+ const { setBreadcrumbs, isSidebarEnabled$ } = dependencies;
const [selectedId, setSelectedId] = useState('');
const [sections, setSections] = useState();
+ const isSidebarEnabled = useObservable(isSidebarEnabled$);
const onAppMounted = useCallback((id: string) => {
setSelectedId(id);
@@ -75,18 +80,20 @@ export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppPr
return null;
}
- const solution: KibanaPageTemplateProps['solutionNav'] = {
- name: i18n.translate('management.nav.label', {
- defaultMessage: 'Management',
- }),
- icon: 'managementApp',
- 'data-test-subj': 'mgtSideBarNav',
- items: managementSidebarNav({
- selectedId,
- sections,
- history,
- }),
- };
+ const solution: KibanaPageTemplateProps['solutionNav'] | undefined = isSidebarEnabled
+ ? {
+ name: i18n.translate('management.nav.label', {
+ defaultMessage: 'Management',
+ }),
+ icon: 'managementApp',
+ 'data-test-subj': 'mgtSideBarNav',
+ items: managementSidebarNav({
+ selectedId,
+ sections,
+ history,
+ }),
+ }
+ : undefined;
return (
diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts
index 92f6f9e1ed4ce..ec4ec2fa7883a 100644
--- a/src/plugins/management/public/mocks/index.ts
+++ b/src/plugins/management/public/mocks/index.ts
@@ -42,7 +42,7 @@ const createSetupContract = (): ManagementSetup => ({
});
const createStartContract = (): ManagementStart => ({
- sections: {},
+ setIsSidebarEnabled: jest.fn(),
});
export const managementPluginMock = {
diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts
index 0cdb83d4b6793..8310074a63159 100644
--- a/src/plugins/management/public/plugin.ts
+++ b/src/plugins/management/public/plugin.ts
@@ -71,11 +71,14 @@ export class ManagementPlugin
private hasAnyEnabledApps = true;
+ private isSidebarEnabled$ = new BehaviorSubject(true);
+
constructor(private initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup, { home, share }: ManagementSetupDependencies) {
const kibanaVersion = this.initializerContext.env.packageInfo.version;
const locator = share.url.locators.create(new ManagementAppLocatorDefinition());
+ const managementPlugin = this;
if (home) {
home.featureCatalogue.register({
@@ -111,6 +114,7 @@ export class ManagementPlugin
sections: getSectionsServiceStartPrivate(),
kibanaVersion,
setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
+ isSidebarEnabled$: managementPlugin.isSidebarEnabled$,
});
},
});
@@ -121,7 +125,7 @@ export class ManagementPlugin
};
}
- public start(core: CoreStart, plugins: ManagementStartDependencies) {
+ public start(core: CoreStart, _plugins: ManagementStartDependencies): ManagementStart {
this.managementSections.start({ capabilities: core.application.capabilities });
this.hasAnyEnabledApps = getSectionsServiceStartPrivate()
.getSectionsEnabled()
@@ -136,6 +140,9 @@ export class ManagementPlugin
});
}
- return {};
+ return {
+ setIsSidebarEnabled: (isSidebarEnabled: boolean) =>
+ this.isSidebarEnabled$.next(isSidebarEnabled),
+ };
}
}
diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts
index 5d8d963ea981e..0f632b818a950 100644
--- a/src/plugins/management/public/types.ts
+++ b/src/plugins/management/public/types.ts
@@ -27,8 +27,9 @@ export interface DefinedSections {
stack: ManagementSection;
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface ManagementStart {}
+export interface ManagementStart {
+ setIsSidebarEnabled: (enabled: boolean) => void;
+}
export interface ManagementSectionsStartPrivate {
getSectionsEnabled: () => ManagementSection[];
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 58eb34d40debd..4999fc4868696 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1150,6 +1150,8 @@
"@kbn/serverless-project-switcher/*": ["packages/serverless/project_switcher/*"],
"@kbn/serverless-search": ["x-pack/plugins/serverless_search"],
"@kbn/serverless-search/*": ["x-pack/plugins/serverless_search/*"],
+ "@kbn/serverless-security": ["x-pack/plugins/serverless_security"],
+ "@kbn/serverless-security/*": ["x-pack/plugins/serverless_security/*"],
"@kbn/serverless-storybook-config": ["packages/serverless/storybook/config"],
"@kbn/serverless-storybook-config/*": ["packages/serverless/storybook/config/*"],
"@kbn/serverless-types": ["packages/serverless/types"],
diff --git a/x-pack/plugins/security_solution/common/index.ts b/x-pack/plugins/security_solution/common/index.ts
index 85546afccb694..3133996d79ed8 100644
--- a/x-pack/plugins/security_solution/common/index.ts
+++ b/x-pack/plugins/security_solution/common/index.ts
@@ -8,6 +8,7 @@
// TODO(jbudz): should be removed when upgrading to TS@4.8
// this is a skip for the errors created when typechecking with isolatedModules
export {};
+export { APP_UI_ID, SecurityPageName } from './constants';
export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants';
// Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase.
diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts
index 7ea8a9f51f406..e0bd41655545c 100644
--- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts
+++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts
@@ -78,7 +78,7 @@ import {
USERS_PATH,
} from '../../../common/constants';
import type { ExperimentalFeatures } from '../../../common/experimental_features';
-import { hasCapabilities, subscribeAppLinks } from '../../common/links';
+import { appLinks$, hasCapabilities } from '../../common/links';
import type { AppLinkItems } from '../../common/links/types';
export const FEATURE = {
@@ -630,7 +630,7 @@ const formatDeepLinks = (appLinks: AppLinkItems): AppDeepLink[] =>
* Registers any change in appLinks to be updated in app deepLinks
*/
export const registerDeepLinksUpdater = (appUpdater$: Subject): Subscription => {
- return subscribeAppLinks((appLinks) => {
+ return appLinks$.subscribe((appLinks) => {
appUpdater$.next(() => ({
navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent main security link to switch to visible after update
deepLinks: formatDeepLinks(appLinks),
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts
deleted file mode 100644
index c44873414ca11..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 { renderHook } from '@testing-library/react-hooks';
-import { SecurityPageName } from '../../../app/types';
-import type { AppLinkItems } from '../../links';
-import { TestProviders } from '../../mock';
-import { useAppNavLinks, useAppRootNavLink } from './nav_links';
-import type { NavLinkItem } from './types';
-
-const mockNavLinks: AppLinkItems = [
- {
- description: 'description',
- id: SecurityPageName.administration,
- links: [
- {
- description: 'description 2',
- id: SecurityPageName.endpoints,
- links: [],
- path: '/path_2',
- title: 'title 2',
- sideNavDisabled: true,
- landingIcon: 'someicon',
- landingImage: 'someimage',
- skipUrlState: true,
- },
- ],
- path: '/path',
- title: 'title',
- },
-];
-
-jest.mock('../../links', () => ({
- useAppLinks: () => mockNavLinks,
-}));
-
-const renderUseAppNavLinks = () =>
- renderHook<{}, NavLinkItem[]>(() => useAppNavLinks(), { wrapper: TestProviders });
-
-const renderUseAppRootNavLink = (id: SecurityPageName) =>
- renderHook<{ id: SecurityPageName }, NavLinkItem | undefined>(() => useAppRootNavLink(id), {
- wrapper: TestProviders,
- });
-
-describe('useAppNavLinks', () => {
- it('should return all nav links', () => {
- const { result } = renderUseAppNavLinks();
- expect(result.current).toMatchInlineSnapshot(`
- Array [
- Object {
- "description": "description",
- "id": "administration",
- "links": Array [
- Object {
- "description": "description 2",
- "disabled": true,
- "icon": "someicon",
- "id": "endpoints",
- "image": "someimage",
- "skipUrlState": true,
- "title": "title 2",
- },
- ],
- "title": "title",
- },
- ]
- `);
- });
-
- it('should return a root nav links', () => {
- const { result } = renderUseAppRootNavLink(SecurityPageName.administration);
- expect(result.current).toMatchInlineSnapshot(`
- Object {
- "description": "description",
- "id": "administration",
- "links": Array [
- Object {
- "description": "description 2",
- "disabled": true,
- "icon": "someicon",
- "id": "endpoints",
- "image": "someimage",
- "skipUrlState": true,
- "title": "title 2",
- },
- ],
- "title": "title",
- }
- `);
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx
index acf7765ffa936..a0612e9b63897 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx
@@ -51,9 +51,9 @@ jest.mock('../../../links', () => ({
getAncestorLinksInfo: (id: string) => [{ id }],
}));
-const mockUseAppNavLinks = jest.fn();
-jest.mock('../nav_links', () => ({
- useAppNavLinks: () => mockUseAppNavLinks(),
+const mockUseNavLinks = jest.fn();
+jest.mock('../../../links/nav_links', () => ({
+ useNavLinks: () => mockUseNavLinks(),
}));
jest.mock('../../links', () => ({
useGetSecuritySolutionLinkProps:
@@ -80,14 +80,14 @@ const renderNav = () =>
describe('SecuritySideNav', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockUseAppNavLinks.mockReturnValue([alertsNavLink, manageNavLink]);
+ mockUseNavLinks.mockReturnValue([alertsNavLink, manageNavLink]);
useKibana().services.chrome.hasHeaderBanner$ = jest.fn(() =>
new BehaviorSubject(false).asObservable()
);
});
it('should render main items', () => {
- mockUseAppNavLinks.mockReturnValue([alertsNavLink]);
+ mockUseNavLinks.mockReturnValue([alertsNavLink]);
renderNav();
expect(mockSolutionSideNav).toHaveBeenCalledWith({
selectedId: SecurityPageName.alerts,
@@ -104,7 +104,7 @@ describe('SecuritySideNav', () => {
});
it('should render the loader if items are still empty', () => {
- mockUseAppNavLinks.mockReturnValue([]);
+ mockUseNavLinks.mockReturnValue([]);
const result = renderNav();
expect(result.getByTestId('sideNavLoader')).toBeInTheDocument();
expect(mockSolutionSideNav).not.toHaveBeenCalled();
@@ -121,7 +121,7 @@ describe('SecuritySideNav', () => {
});
it('should render footer items', () => {
- mockUseAppNavLinks.mockReturnValue([manageNavLink]);
+ mockUseNavLinks.mockReturnValue([manageNavLink]);
renderNav();
expect(mockSolutionSideNav).toHaveBeenCalledWith(
expect.objectContaining({
@@ -148,7 +148,7 @@ describe('SecuritySideNav', () => {
});
it('should not render disabled items', () => {
- mockUseAppNavLinks.mockReturnValue([{ ...alertsNavLink, disabled: true }, manageNavLink]);
+ mockUseNavLinks.mockReturnValue([{ ...alertsNavLink, disabled: true }, manageNavLink]);
renderNav();
expect(mockSolutionSideNav).toHaveBeenCalledWith(
expect.objectContaining({
@@ -163,7 +163,7 @@ describe('SecuritySideNav', () => {
});
it('should render custom item', () => {
- mockUseAppNavLinks.mockReturnValue([{ id: SecurityPageName.landing, title: 'get started' }]);
+ mockUseNavLinks.mockReturnValue([{ id: SecurityPageName.landing, title: 'get started' }]);
renderNav();
expect(mockSolutionSideNav).toHaveBeenCalledWith(
expect.objectContaining({
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx
index f647ff1f873e1..b34e069c24860 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx
@@ -13,7 +13,7 @@ import { SecurityPageName } from '../../../../app/types';
import { getAncestorLinksInfo } from '../../../links';
import { useRouteSpy } from '../../../utils/route/use_route_spy';
import { useGetSecuritySolutionLinkProps } from '../../links';
-import { useAppNavLinks } from '../nav_links';
+import { useNavLinks } from '../../../links/nav_links';
import { useShowTimeline } from '../../../utils/timeline/use_show_timeline';
import { useIsPolicySettingsBarVisible } from '../../../../management/pages/policy/view/policy_hooks';
import { track } from '../../../lib/telemetry';
@@ -30,7 +30,7 @@ const isGetStartedNavItem = (id: SecurityPageName) => id === SecurityPageName.la
* Returns the formatted `items` and `footerItems` to be rendered in the navigation
*/
const useSolutionSideNavItems = () => {
- const navLinks = useAppNavLinks();
+ const navLinks = useNavLinks();
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); // adds href and onClick props
const sideNavItems = useMemo(() => {
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx
index 647193357b66b..867cda2bcf4e8 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx
@@ -9,10 +9,12 @@ import React, { useEffect, useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
+import useObservable from 'react-use/lib/useObservable';
import type { PrimaryNavigationProps } from './types';
import { usePrimaryNavigationItems } from './use_navigation_items';
import { useIsGroupedNavigationEnabled } from '../helpers';
import { SecuritySideNav } from '../security_side_nav';
+import { useKibana } from '../../../lib/kibana';
const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', {
defaultMessage: 'Security',
@@ -45,6 +47,14 @@ export const usePrimaryNavigation = ({
selectedTabId,
});
+ const { isSidebarEnabled$ } = useKibana().services;
+
+ const isSidebarEnabled = useObservable(isSidebarEnabled$);
+
+ if (!isSidebarEnabled) {
+ return undefined;
+ }
+
return {
canBeCollapsed: true,
name: translatedNavTitle,
diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts
index 42a34fa08fce3..0383b4a700420 100644
--- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts
@@ -45,6 +45,7 @@ import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mo
import { mockApm } from '../apm/service.mock';
import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks';
import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks';
+import { of } from 'rxjs';
const mockUiSettings: Record = {
[DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' },
@@ -183,6 +184,7 @@ export const createStartServicesMock = (
triggersActionsUi,
cloudExperiments,
guidedOnboarding,
+ isSidebarEnabled$: of(true),
} as unknown as StartServices;
};
diff --git a/x-pack/plugins/security_solution/public/common/links/links.ts b/x-pack/plugins/security_solution/public/common/links/links.ts
index de30840d02d9d..be1f206e339b2 100644
--- a/x-pack/plugins/security_solution/public/common/links/links.ts
+++ b/x-pack/plugins/security_solution/public/common/links/links.ts
@@ -7,7 +7,8 @@
import type { Capabilities } from '@kbn/core/public';
import { get, isArray } from 'lodash';
-import { useEffect, useState } from 'react';
+import { useMemo } from 'react';
+import useObservable from 'react-use/lib/useObservable';
import { BehaviorSubject } from 'rxjs';
import type { SecurityPageName } from '../../../common/constants';
import type {
@@ -20,72 +21,48 @@ import type {
} from './types';
/**
- * App links updater, it keeps the value of the app links in sync with all application.
- * It can be updated using `updateAppLinks` or `excludeAppLink`
- * Read it using `subscribeAppLinks` or `useAppLinks` hook.
+ * App links updater, it stores the `appLinkItems` recursive hierarchy and keeps
+ * the value of the app links in sync with all application components.
+ * It can be updated using `updateAppLinks`.
+ * Read it using subscription or `useAppLinks` hook.
*/
-const appLinksUpdater$ = new BehaviorSubject<{
- links: AppLinkItems;
- normalizedLinks: NormalizedLinks;
-}>({
- links: [], // stores the appLinkItems recursive hierarchy
- normalizedLinks: {}, // stores a flatten normalized object for direct id access
-});
+const appLinksUpdater$ = new BehaviorSubject([]);
+// stores a flatten normalized appLinkItems object for internal direct id access
+const normalizedAppLinksUpdater$ = new BehaviorSubject({});
-const getAppLinksValue = (): AppLinkItems => appLinksUpdater$.getValue().links;
-const getNormalizedLinksValue = (): NormalizedLinks => appLinksUpdater$.getValue().normalizedLinks;
+// AppLinks observable
+export const appLinks$ = appLinksUpdater$.asObservable();
/**
- * Subscribes to the updater to get the app links updates
+ * Updates the app links applying the filter by permissions
*/
-export const subscribeAppLinks = (onChange: (links: AppLinkItems) => void) =>
- appLinksUpdater$.subscribe(({ links }) => onChange(links));
+export const updateAppLinks = (
+ appLinksToUpdate: AppLinkItems,
+ linksPermissions: LinksPermissions
+) => {
+ const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions);
+ appLinksUpdater$.next(Object.freeze(filteredAppLinks));
+ normalizedAppLinksUpdater$.next(Object.freeze(getNormalizedLinks(filteredAppLinks)));
+};
/**
* Hook to get the app links updated value
*/
-export const useAppLinks = (): AppLinkItems => {
- const [appLinks, setAppLinks] = useState(getAppLinksValue);
-
- useEffect(() => {
- const linksSubscription = subscribeAppLinks((newAppLinks) => {
- setAppLinks(newAppLinks);
- });
- return () => linksSubscription.unsubscribe();
- }, []);
-
- return appLinks;
-};
+export const useAppLinks = (): AppLinkItems =>
+ useObservable(appLinksUpdater$, appLinksUpdater$.getValue());
+/**
+ * Hook to get the normalized app links updated value
+ */
+export const useNormalizedAppLinks = (): NormalizedLinks =>
+ useObservable(normalizedAppLinksUpdater$, normalizedAppLinksUpdater$.getValue());
/**
* Hook to check if a link exists in the application links,
* It can be used to know if a link access is authorized.
*/
export const useLinkExists = (id: SecurityPageName): boolean => {
- const [linkExists, setLinkExists] = useState(!!getNormalizedLink(id));
-
- useEffect(() => {
- const linksSubscription = subscribeAppLinks(() => {
- setLinkExists(!!getNormalizedLink(id));
- });
- return () => linksSubscription.unsubscribe();
- }, [id]);
-
- return linkExists;
-};
-
-/**
- * Updates the app links applying the filter by permissions
- */
-export const updateAppLinks = (
- appLinksToUpdate: AppLinkItems,
- linksPermissions: LinksPermissions
-) => {
- const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions);
- appLinksUpdater$.next({
- links: Object.freeze(filteredAppLinks),
- normalizedLinks: Object.freeze(getNormalizedLinks(filteredAppLinks)),
- });
+ const normalizedLinks = useNormalizedAppLinks();
+ return useMemo(() => !!normalizedLinks[id], [normalizedLinks, id]);
};
/**
@@ -128,6 +105,10 @@ export const needsUrlState = (id: SecurityPageName): boolean => {
return !getNormalizedLink(id)?.skipUrlState;
};
+export const getLinksWithHiddenTimeline = (): LinkInfo[] => {
+ return Object.values(normalizedAppLinksUpdater$.getValue()).filter((link) => link.hideTimeline);
+};
+
// Internal functions
/**
@@ -136,8 +117,8 @@ export const needsUrlState = (id: SecurityPageName): boolean => {
const getNormalizedLinks = (
currentLinks: AppLinkItems,
parentId?: SecurityPageName
-): NormalizedLinks => {
- return currentLinks.reduce((normalized, { links, ...currentLink }) => {
+): NormalizedLinks =>
+ currentLinks.reduce((normalized, { links, ...currentLink }) => {
normalized[currentLink.id] = {
...currentLink,
parentId,
@@ -147,10 +128,9 @@ const getNormalizedLinks = (
}
return normalized;
}, {});
-};
const getNormalizedLink = (id: SecurityPageName): Readonly | undefined =>
- getNormalizedLinksValue()[id];
+ normalizedAppLinksUpdater$.getValue()[id];
const getFilteredAppLinks = (
appLinkToFilter: AppLinkItems,
@@ -226,7 +206,3 @@ const isLinkAllowed = (
}
return true;
};
-
-export const getLinksWithHiddenTimeline = (): LinkInfo[] => {
- return Object.values(getNormalizedLinksValue()).filter((link) => link.hideTimeline);
-};
diff --git a/x-pack/plugins/security_solution/public/common/links/nav_links.test.ts b/x-pack/plugins/security_solution/public/common/links/nav_links.test.ts
new file mode 100644
index 0000000000000..d8decac43a86a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/links/nav_links.test.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 type { AppLinkItems } from './types';
+import { formatNavigationLinks } from './nav_links';
+import { SecurityPageName } from '../../app/types';
+
+const mockNavLinks: AppLinkItems = [
+ {
+ description: 'description',
+ id: SecurityPageName.administration,
+ links: [
+ {
+ description: 'description 2',
+ id: SecurityPageName.endpoints,
+ links: [],
+ path: '/path_2',
+ title: 'title 2',
+ sideNavDisabled: true,
+ landingIcon: 'someicon',
+ landingImage: 'someimage',
+ skipUrlState: true,
+ },
+ ],
+ path: '/path',
+ title: 'title',
+ },
+];
+
+describe('formatNavigationLinks', () => {
+ it('should format links', () => {
+ expect(formatNavigationLinks(mockNavLinks)).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "description": "description",
+ "id": "administration",
+ "links": Array [
+ Object {
+ "description": "description 2",
+ "disabled": true,
+ "icon": "someicon",
+ "id": "endpoints",
+ "image": "someimage",
+ "skipUrlState": true,
+ "title": "title 2",
+ },
+ ],
+ "title": "title",
+ },
+ ]
+ `);
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts b/x-pack/plugins/security_solution/public/common/links/nav_links.ts
similarity index 52%
rename from x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts
rename to x-pack/plugins/security_solution/public/common/links/nav_links.ts
index 5fff0a9649940..0882bdf233601 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts
+++ b/x-pack/plugins/security_solution/public/common/links/nav_links.ts
@@ -5,23 +5,13 @@
* 2.0.
*/
-import { useMemo } from 'react';
-import { useAppLinks } from '../../links';
-import type { SecurityPageName } from '../../../app/types';
-import type { NavLinkItem } from './types';
-import type { AppLinkItems } from '../../links/types';
+import useObservable from 'react-use/lib/useObservable';
+import { map } from 'rxjs';
+import { appLinks$ } from './links';
+import type { SecurityPageName } from '../../app/types';
+import type { AppLinkItems, NavigationLink } from './types';
-export const useAppNavLinks = (): NavLinkItem[] => {
- const appLinks = useAppLinks();
- const navLinks = useMemo(() => formatNavLinkItems(appLinks), [appLinks]);
- return navLinks;
-};
-
-export const useAppRootNavLink = (linkId: SecurityPageName): NavLinkItem | undefined => {
- return useAppNavLinks().find(({ id }) => id === linkId);
-};
-
-const formatNavLinkItems = (appLinks: AppLinkItems): NavLinkItem[] =>
+export const formatNavigationLinks = (appLinks: AppLinkItems): NavigationLink[] =>
appLinks.map((link) => ({
id: link.id,
title: link.title,
@@ -33,9 +23,21 @@ const formatNavLinkItems = (appLinks: AppLinkItems): NavLinkItem[] =>
...(link.skipUrlState != null ? { skipUrlState: link.skipUrlState } : {}),
...(link.isBeta != null ? { isBeta: link.isBeta } : {}),
...(link.betaOptions != null ? { betaOptions: link.betaOptions } : {}),
- ...(link.links && link.links.length
- ? {
- links: formatNavLinkItems(link.links),
- }
- : {}),
+ ...(link.links?.length && {
+ links: formatNavigationLinks(link.links),
+ }),
}));
+
+/**
+ * Navigation links observable based on Security AppLinks,
+ * It is used to generate the side navigation items
+ */
+export const navLinks$ = appLinks$.pipe(map(formatNavigationLinks));
+
+export const useNavLinks = (): NavigationLink[] => {
+ return useObservable(navLinks$, []);
+};
+
+export const useRootNavLink = (linkId: SecurityPageName): NavigationLink | undefined => {
+ return useNavLinks().find(({ id }) => id === linkId);
+};
diff --git a/x-pack/plugins/security_solution/public/common/links/types.ts b/x-pack/plugins/security_solution/public/common/links/types.ts
index f9a2c5776262f..162415fb66a16 100644
--- a/x-pack/plugins/security_solution/public/common/links/types.ts
+++ b/x-pack/plugins/security_solution/public/common/links/types.ts
@@ -131,3 +131,19 @@ export type AppLinkItems = Readonly;
export type LinkInfo = Omit;
export type NormalizedLink = LinkInfo & { parentId?: SecurityPageName };
export type NormalizedLinks = Partial>;
+
+export interface NavigationLink {
+ categories?: LinkCategories;
+ description?: string;
+ disabled?: boolean;
+ icon?: IconType;
+ id: SecurityPageName;
+ links?: NavigationLink[];
+ image?: string;
+ title: string;
+ skipUrlState?: boolean;
+ isBeta?: boolean;
+ betaOptions?: {
+ text: string;
+ };
+}
diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx
index bfc035a0add12..7ea6ae6198ccb 100644
--- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx
@@ -49,8 +49,8 @@ const APP_DASHBOARD_LINKS: NavLinkItem = {
const URL = '/path/to/dashboards';
const mockAppManageLink = jest.fn(() => APP_DASHBOARD_LINKS);
-jest.mock('../../../common/components/navigation/nav_links', () => ({
- useAppRootNavLink: () => mockAppManageLink(),
+jest.mock('../../../common/links/nav_links', () => ({
+ useRootNavLink: () => mockAppManageLink(),
}));
const CREATE_DASHBOARD_LINK = { isLoading: false, url: URL };
diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx
index d6d9be365fc30..4e1e48d09793c 100644
--- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx
+++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx
@@ -14,7 +14,7 @@ import { DashboardsTable } from '../../../common/components/dashboards/dashboard
import { LandingImageCards } from '../../../landing_pages/components/landing_links_images';
import { SecurityPageName } from '../../../../common/constants';
import { useCapabilities, useNavigateTo } from '../../../common/lib/kibana';
-import { useAppRootNavLink } from '../../../common/components/navigation/nav_links';
+import { useRootNavLink } from '../../../common/links/nav_links';
import { useCreateSecurityDashboardLink } from '../../../common/containers/dashboards/use_create_security_dashboard_link';
import { Title } from '../../../common/components/header_page/title';
import { LinkButton } from '../../../common/components/links/helpers';
@@ -54,7 +54,7 @@ const Header: React.FC<{ canCreateDashboard: boolean }> = ({ canCreateDashboard
};
export const DashboardsLandingPage = () => {
- const dashboardLinks = useAppRootNavLink(SecurityPageName.dashboards)?.links ?? [];
+ const dashboardLinks = useRootNavLink(SecurityPageName.dashboards)?.links ?? [];
const { show: canReadDashboard, createNew: canCreateDashboard } =
useCapabilities(LEGACY_DASHBOARD_APP_ID);
diff --git a/x-pack/plugins/security_solution/public/index.ts b/x-pack/plugins/security_solution/public/index.ts
index 1f6f121e04209..7ac596d087fca 100644
--- a/x-pack/plugins/security_solution/public/index.ts
+++ b/x-pack/plugins/security_solution/public/index.ts
@@ -7,10 +7,10 @@
import type { PluginInitializerContext } from '@kbn/core/public';
import { Plugin } from './plugin';
-import type { PluginSetup } from './types';
+import type { PluginSetup, PluginStart } from './types';
export type { TimelineModel } from './timelines/store/timeline/model';
export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context);
-export type { PluginSetup };
+export type { PluginSetup, PluginStart };
export { Plugin };
diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx
index 17a0d7569b965..26dd3009e1d03 100644
--- a/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx
+++ b/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx
@@ -7,14 +7,14 @@
import React from 'react';
import { SecurityPageName } from '../../app/types';
import { HeaderPage } from '../../common/components/header_page';
-import { useAppRootNavLink } from '../../common/components/navigation/nav_links';
+import { useRootNavLink } from '../../common/links/nav_links';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { LandingLinksImages } from '../components/landing_links_images';
import { EXPLORE_PAGE_TITLE } from './translations';
export const ExploreLandingPage = () => {
- const exploreLinks = useAppRootNavLink(SecurityPageName.exploreLanding)?.links ?? [];
+ const exploreLinks = useRootNavLink(SecurityPageName.exploreLanding)?.links ?? [];
return (
diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx
index 67eb06b60cca6..e900fad75546a 100644
--- a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx
+++ b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx
@@ -10,14 +10,14 @@ import React from 'react';
import { SecurityPageName } from '../../app/types';
import { TestProviders } from '../../common/mock';
import { ManagementCategories } from './manage';
-import type { NavLinkItem } from '../../common/components/navigation/types';
+import type { NavigationLink } from '../../common/links';
const RULES_ITEM_LABEL = 'elastic rules!';
const EXCEPTIONS_ITEM_LABEL = 'exceptional!';
const CATEGORY_1_LABEL = 'first tests category';
const CATEGORY_2_LABEL = 'second tests category';
-const defaultAppManageLink: NavLinkItem = {
+const defaultAppManageLink: NavigationLink = {
id: SecurityPageName.administration,
title: 'admin',
categories: [
@@ -47,8 +47,8 @@ const defaultAppManageLink: NavLinkItem = {
};
const mockAppManageLink = jest.fn(() => defaultAppManageLink);
-jest.mock('../../common/components/navigation/nav_links', () => ({
- useAppRootNavLink: () => mockAppManageLink(),
+jest.mock('../../common/links/nav_links', () => ({
+ useRootNavLink: () => mockAppManageLink(),
}));
describe('ManagementCategories', () => {
diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx
index cb77921a0b673..37e2391801cac 100644
--- a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx
+++ b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx
@@ -10,8 +10,8 @@ import styled from 'styled-components';
import { SecurityPageName } from '../../app/types';
import { HeaderPage } from '../../common/components/header_page';
-import { useAppRootNavLink } from '../../common/components/navigation/nav_links';
-import type { NavLinkItem } from '../../common/components/navigation/types';
+import { useRootNavLink } from '../../common/links/nav_links';
+import type { NavigationLink } from '../../common/links';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { LandingLinksIcons } from '../components/landing_links_icons';
@@ -30,14 +30,14 @@ const StyledEuiHorizontalRule = styled(EuiHorizontalRule)`
margin-bottom: ${({ theme }) => theme.eui.euiSizeL};
`;
-type ManagementCategories = Array<{ label: string; links: NavLinkItem[] }>;
+type ManagementCategories = Array<{ label: string; links: NavigationLink[] }>;
const useManagementCategories = (): ManagementCategories => {
- const { links = [], categories = [] } = useAppRootNavLink(SecurityPageName.administration) ?? {};
+ const { links = [], categories = [] } = useRootNavLink(SecurityPageName.administration) ?? {};
const manageLinksById = Object.fromEntries(links.map((link) => [link.id, link]));
return categories.reduce((acc, { label, linkIds }) => {
- const linksItem = linkIds.reduce((linksAcc, linkId) => {
+ const linksItem = linkIds.reduce((linksAcc, linkId) => {
if (manageLinksById[linkId]) {
linksAcc.push(manageLinksById[linkId]);
}
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts
index 3100d4a64fec7..63d9f26514ba5 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts
@@ -15,7 +15,8 @@ import { navigateToFleetAgentDetails } from '../../screens/fleet';
import { EndpointPolicyResponseGenerator } from '../../../../../common/endpoint/data_generators/endpoint_policy_response_generator';
import { descriptions } from '../../../components/policy_response/policy_response_friendly_names';
-describe('Endpoint Policy Response', () => {
+// FLAKY: https://github.com/elastic/security-team/issues/6518
+describe.skip('Endpoint Policy Response', () => {
let loadedEndpoint: CyIndexEndpointHosts;
let endpointMetadata: HostMetadata;
let loadedPolicyResponse: IndexedEndpointPolicyResponse;
diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx
index 61ec29d956414..14b886920613b 100644
--- a/x-pack/plugins/security_solution/public/plugin.tsx
+++ b/x-pack/plugins/security_solution/public/plugin.tsx
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import type { Subscription } from 'rxjs';
-import { Subject } from 'rxjs';
+import { BehaviorSubject, Subject } from 'rxjs';
import { combineLatestWith } from 'rxjs/operators';
import type * as H from 'history';
import type {
@@ -45,6 +45,7 @@ import {
import { getDeepLinks, registerDeepLinksUpdater } from './app/deep_links';
import type { LinksPermissions } from './common/links';
import { updateAppLinks } from './common/links';
+import { navLinks$ } from './common/links/nav_links';
import { licenseService } from './common/hooks/use_license';
import type { SecuritySolutionUiConfigType } from './common/types';
import { ExperimentalFeaturesService } from './common/experimental_features_service';
@@ -86,6 +87,7 @@ export class Plugin implements IPlugin;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get();
@@ -93,7 +95,7 @@ export class Plugin implements IPlugin(true);
this.telemetry = new TelemetryService();
}
private appUpdater$ = new Subject();
@@ -168,6 +170,7 @@ export class Plugin implements IPlugin SecuritySolutionTemplateWrapper,
},
savedObjectsManagement: startPluginsDeps.savedObjectsManagement,
+ isSidebarEnabled$: this.isSidebarEnabled$,
telemetry: this.telemetry.start(),
};
return services;
@@ -245,7 +248,7 @@ export class Plugin implements IPlugin
+ this.isSidebarEnabled$.next(isSidebarEnabled),
+ };
}
public stop() {
diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts
index e6e10a9c5b3dd..ba5bd460ba376 100644
--- a/x-pack/plugins/security_solution/public/types.ts
+++ b/x-pack/plugins/security_solution/public/types.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import type { BehaviorSubject, Observable } from 'rxjs';
+
import type { AppLeaveHandler, CoreStart } from '@kbn/core/public';
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
@@ -46,6 +48,7 @@ import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public';
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
+
import type { ResolverPluginSetup } from './resolver/types';
import type { Inspect } from '../common/search_strategy';
import type { Detections } from './detections';
@@ -62,6 +65,8 @@ import type { CloudDefend } from './cloud_defend';
import type { ThreatIntelligence } from './threat_intelligence';
import type { SecuritySolutionTemplateWrapper } from './app/home/template_wrapper';
import type { Explore } from './explore';
+import type { NavigationLink } from './common/links';
+
import type { TelemetryClientStart } from './common/lib/telemetry';
import type { Dashboards } from './dashboards';
@@ -127,14 +132,18 @@ export type StartServices = CoreStart &
getPluginWrapper: () => typeof SecuritySolutionTemplateWrapper;
};
savedObjectsManagement: SavedObjectsManagementPluginStart;
+ isSidebarEnabled$: BehaviorSubject;
telemetry: TelemetryClientStart;
};
export interface PluginSetup {
resolver: () => Promise;
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface PluginStart {}
+
+export interface PluginStart {
+ navLinks$: Observable;
+ setIsSidebarEnabled: (isSidebarEnabled: boolean) => void;
+}
export interface AppObservableLibs {
kibana: CoreStart;
diff --git a/x-pack/plugins/serverless/kibana.jsonc b/x-pack/plugins/serverless/kibana.jsonc
index af79877f1079f..5a724f7641a6a 100644
--- a/x-pack/plugins/serverless/kibana.jsonc
+++ b/x-pack/plugins/serverless/kibana.jsonc
@@ -14,8 +14,9 @@
],
"requiredPlugins": [
"kibanaReact",
+ "management",
],
"optionalPlugins": [],
"requiredBundles": []
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx
index 9c9debe3e9f21..20f4634fb7fa0 100644
--- a/x-pack/plugins/serverless/public/plugin.tsx
+++ b/x-pack/plugins/serverless/public/plugin.tsx
@@ -13,23 +13,43 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/cor
import { ProjectSwitcher, ProjectSwitcherKibanaProvider } from '@kbn/serverless-project-switcher';
import { ProjectType } from '@kbn/serverless-types';
-import { ServerlessPluginSetup, ServerlessPluginStart } from './types';
+import {
+ ServerlessPluginSetup,
+ ServerlessPluginStart,
+ ServerlessPluginSetupDependencies,
+ ServerlessPluginStartDependencies,
+} from './types';
import { ServerlessConfig } from './config';
import { API_SWITCH_PROJECT as projectChangeAPIUrl } from '../common';
-export class ServerlessPlugin implements Plugin {
+export class ServerlessPlugin
+ implements
+ Plugin<
+ ServerlessPluginSetup,
+ ServerlessPluginStart,
+ ServerlessPluginSetupDependencies,
+ ServerlessPluginStartDependencies
+ >
+{
private readonly config: ServerlessConfig;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get();
}
- public setup(_core: CoreSetup): ServerlessPluginSetup {
+ public setup(
+ _core: CoreSetup,
+ _dependencies: ServerlessPluginSetupDependencies
+ ): ServerlessPluginSetup {
return {};
}
- public start(core: CoreStart): ServerlessPluginStart {
+ public start(
+ core: CoreStart,
+ dependencies: ServerlessPluginStartDependencies
+ ): ServerlessPluginStart {
const { developer } = this.config;
+ const { management } = dependencies;
if (developer && developer.projectSwitcher && developer.projectSwitcher.enabled) {
const { currentType } = developer.projectSwitcher;
@@ -40,6 +60,7 @@ export class ServerlessPlugin implements Plugin
+{
+ public setup(
+ _core: CoreSetup,
+ _setupDeps: ServerlessSecurityPluginSetupDependencies
+ ): ServerlessSecurityPluginSetup {
+ return {};
+ }
+
+ public start(
+ _core: CoreStart,
+ { securitySolution }: ServerlessSecurityPluginStartDependencies
+ ): ServerlessSecurityPluginStart {
+ securitySolution.setIsSidebarEnabled(false);
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/serverless_security/public/types.ts b/x-pack/plugins/serverless_security/public/types.ts
new file mode 100644
index 0000000000000..1fc18893ce1fd
--- /dev/null
+++ b/x-pack/plugins/serverless_security/public/types.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
+import {
+ PluginSetup as SecuritySolutionPluginSetup,
+ PluginStart as SecuritySolutionPluginStart,
+} from '@kbn/security-solution-plugin/public';
+import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface ServerlessSecurityPluginSetup {}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface ServerlessSecurityPluginStart {}
+
+export interface ServerlessSecurityPluginSetupDependencies {
+ security: SecurityPluginSetup;
+ securitySolution: SecuritySolutionPluginSetup;
+ serverless: ServerlessPluginSetup;
+}
+
+export interface ServerlessSecurityPluginStartDependencies {
+ security: SecurityPluginStart;
+ securitySolution: SecuritySolutionPluginStart;
+ serverless: ServerlessPluginStart;
+}
diff --git a/x-pack/plugins/serverless_security/server/config.ts b/x-pack/plugins/serverless_security/server/config.ts
new file mode 100644
index 0000000000000..758b22de1514e
--- /dev/null
+++ b/x-pack/plugins/serverless_security/server/config.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { schema, TypeOf } from '@kbn/config-schema';
+import { PluginConfigDescriptor } from '@kbn/core/server';
+
+export * from './types';
+
+const configSchema = schema.object({
+ enabled: schema.boolean({ defaultValue: false }),
+});
+
+type ConfigType = TypeOf;
+
+export const config: PluginConfigDescriptor = {
+ schema: configSchema,
+};
+
+export type ServerlessSecurityConfig = TypeOf;
diff --git a/x-pack/plugins/serverless_security/server/index.ts b/x-pack/plugins/serverless_security/server/index.ts
new file mode 100644
index 0000000000000..b2b6c8564f788
--- /dev/null
+++ b/x-pack/plugins/serverless_security/server/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { PluginInitializerContext } from '@kbn/core/server';
+
+import { ServerlessSecurityPlugin } from './plugin';
+export { config } from './config';
+
+// This exports static code and TypeScript types,
+// as well as, Kibana Platform `plugin()` initializer.
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new ServerlessSecurityPlugin(initializerContext);
+}
+
+export type { ServerlessSecurityPluginSetup, ServerlessSecurityPluginStart } from './types';
diff --git a/x-pack/plugins/serverless_security/server/plugin.ts b/x-pack/plugins/serverless_security/server/plugin.ts
new file mode 100644
index 0000000000000..7dfbae3b64a67
--- /dev/null
+++ b/x-pack/plugins/serverless_security/server/plugin.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { PluginInitializerContext, Plugin } from '@kbn/core/server';
+
+import {
+ ServerlessSecurityPluginSetup,
+ ServerlessSecurityPluginStart,
+ ServerlessSecurityPluginSetupDependencies,
+ ServerlessSecurityPluginStartDependencies,
+} from './types';
+
+export class ServerlessSecurityPlugin
+ implements
+ Plugin<
+ ServerlessSecurityPluginSetup,
+ ServerlessSecurityPluginStart,
+ ServerlessSecurityPluginSetupDependencies,
+ ServerlessSecurityPluginStartDependencies
+ >
+{
+ constructor(_initializerContext: PluginInitializerContext) {}
+
+ public setup() {
+ return {};
+ }
+
+ public start() {
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/serverless_security/server/types.ts b/x-pack/plugins/serverless_security/server/types.ts
new file mode 100644
index 0000000000000..be7167d030315
--- /dev/null
+++ b/x-pack/plugins/serverless_security/server/types.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server';
+import {
+ PluginSetup as SecuritySolutionPluginSetup,
+ PluginStart as SecuritySolutionPluginStart,
+} from '@kbn/security-solution-plugin/server';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface ServerlessSecurityPluginSetup {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface ServerlessSecurityPluginStart {}
+
+export interface ServerlessSecurityPluginSetupDependencies {
+ security: SecurityPluginSetup;
+ securitySolution: SecuritySolutionPluginSetup;
+}
+
+export interface ServerlessSecurityPluginStartDependencies {
+ security: SecurityPluginStart;
+ securitySolution: SecuritySolutionPluginStart;
+}
diff --git a/x-pack/plugins/serverless_security/tsconfig.json b/x-pack/plugins/serverless_security/tsconfig.json
new file mode 100644
index 0000000000000..ddf77b288e628
--- /dev/null
+++ b/x-pack/plugins/serverless_security/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "target/types"
+ },
+ "include": [
+ "index.ts",
+ "common/**/*.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "server/**/*.ts",
+ "../../../typings/**/*"
+ ],
+ "exclude": [
+ "target/**/*"
+ ],
+ "kbn_references": [
+ "@kbn/core",
+ "@kbn/config-schema",
+ "@kbn/security-plugin",
+ "@kbn/security-solution-plugin",
+ "@kbn/serverless",
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index b9ef08891dd5e..2253ee337d4cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5042,6 +5042,10 @@
version "0.0.0"
uid ""
+"@kbn/serverless-security@link:x-pack/plugins/serverless_security":
+ version "0.0.0"
+ uid ""
+
"@kbn/serverless@link:x-pack/plugins/serverless":
version "0.0.0"
uid ""