mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[serverless] Create Security Serverless plugin (#156104)
> Derived from https://github.com/elastic/kibana/pull/153274 > Builds upon https://github.com/elastic/kibana/pull/155582 ## Summary This PR creates the Serverless Security plugin, based on the work from https://github.com/elastic/kibana/pull/153274: - creates the plugin, - adds API to hide the solution navigation from Security, - calls that API if the chrome style is `project`. <img width="1688" alt="Screenshot 2023-04-27 at 12 37 46 PM" src="https://user-images.githubusercontent.com/297604/234979670-425bfb12-8194-4916-8f92-efff7804b577.png"> ## Next Steps - render the left nav from https://github.com/elastic/kibana/pull/153274 using an API provided by @elastic/appex-sharedux - this low-level API should be coming in the next few days. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a16930fc5b
commit
b217dbf001
49 changed files with 572 additions and 252 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -118,6 +118,7 @@ pageLoadAssetSize:
|
|||
serverless: 16573
|
||||
serverlessObservability: 16582
|
||||
serverlessSearch: 17548
|
||||
serverlessSecurity: 41807
|
||||
sessionView: 77750
|
||||
share: 71239
|
||||
snapshotRestore: 79032
|
||||
|
|
|
@ -16,15 +16,6 @@ export const renderApp = async (
|
|||
{ history, appBasePath, element, theme$ }: AppMountParameters,
|
||||
dependencies: ManagementAppDependencies
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<ManagementApp
|
||||
dependencies={dependencies}
|
||||
appBasePath={appBasePath}
|
||||
history={history}
|
||||
theme$={theme$}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
||||
ReactDOM.render(<ManagementApp {...{ history, appBasePath, theme$, dependencies }} />, element);
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
|
|
|
@ -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<boolean>;
|
||||
}
|
||||
|
||||
export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppProps) => {
|
||||
const { setBreadcrumbs } = dependencies;
|
||||
const { setBreadcrumbs, isSidebarEnabled$ } = dependencies;
|
||||
const [selectedId, setSelectedId] = useState<string>('');
|
||||
const [sections, setSections] = useState<ManagementSection[]>();
|
||||
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 (
|
||||
<I18nProvider>
|
||||
|
|
|
@ -42,7 +42,7 @@ const createSetupContract = (): ManagementSetup => ({
|
|||
});
|
||||
|
||||
const createStartContract = (): ManagementStart => ({
|
||||
sections: {},
|
||||
setIsSidebarEnabled: jest.fn(),
|
||||
});
|
||||
|
||||
export const managementPluginMock = {
|
||||
|
|
|
@ -71,11 +71,14 @@ export class ManagementPlugin
|
|||
|
||||
private hasAnyEnabledApps = true;
|
||||
|
||||
private isSidebarEnabled$ = new BehaviorSubject<boolean>(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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<AppUpdater>): 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),
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -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({
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<string, unknown> = {
|
||||
[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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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,59 +21,17 @@ 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<AppLinkItems>([]);
|
||||
// stores a flatten normalized appLinkItems object for internal direct id access
|
||||
const normalizedAppLinksUpdater$ = new BehaviorSubject<NormalizedLinks>({});
|
||||
|
||||
const getAppLinksValue = (): AppLinkItems => appLinksUpdater$.getValue().links;
|
||||
const getNormalizedLinksValue = (): NormalizedLinks => appLinksUpdater$.getValue().normalizedLinks;
|
||||
|
||||
/**
|
||||
* Subscribes to the updater to get the app links updates
|
||||
*/
|
||||
export const subscribeAppLinks = (onChange: (links: AppLinkItems) => void) =>
|
||||
appLinksUpdater$.subscribe(({ links }) => onChange(links));
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
// AppLinks observable
|
||||
export const appLinks$ = appLinksUpdater$.asObservable();
|
||||
|
||||
/**
|
||||
* Updates the app links applying the filter by permissions
|
||||
|
@ -82,10 +41,28 @@ export const updateAppLinks = (
|
|||
linksPermissions: LinksPermissions
|
||||
) => {
|
||||
const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions);
|
||||
appLinksUpdater$.next({
|
||||
links: Object.freeze(filteredAppLinks),
|
||||
normalizedLinks: Object.freeze(getNormalizedLinks(filteredAppLinks)),
|
||||
});
|
||||
appLinksUpdater$.next(Object.freeze(filteredAppLinks));
|
||||
normalizedAppLinksUpdater$.next(Object.freeze(getNormalizedLinks(filteredAppLinks)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to get the app links updated value
|
||||
*/
|
||||
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 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<NormalizedLinks>((normalized, { links, ...currentLink }) => {
|
||||
): NormalizedLinks =>
|
||||
currentLinks.reduce<NormalizedLinks>((normalized, { links, ...currentLink }) => {
|
||||
normalized[currentLink.id] = {
|
||||
...currentLink,
|
||||
parentId,
|
||||
|
@ -147,10 +128,9 @@ const getNormalizedLinks = (
|
|||
}
|
||||
return normalized;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getNormalizedLink = (id: SecurityPageName): Readonly<NormalizedLink> | 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);
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
};
|
|
@ -131,3 +131,19 @@ export type AppLinkItems = Readonly<LinkItem[]>;
|
|||
export type LinkInfo = Omit<LinkItem, 'links'>;
|
||||
export type NormalizedLink = LinkInfo & { parentId?: SecurityPageName };
|
||||
export type NormalizedLinks = Partial<Record<SecurityPageName, NormalizedLink>>;
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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<DashboardCapabilities>(LEGACY_DASHBOARD_APP_ID);
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 (
|
||||
<SecuritySolutionPageWrapper>
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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<ManagementCategories>((acc, { label, linkIds }) => {
|
||||
const linksItem = linkIds.reduce<NavLinkItem[]>((linksAcc, linkId) => {
|
||||
const linksItem = linkIds.reduce<NavigationLink[]>((linksAcc, linkId) => {
|
||||
if (manageLinksById[linkId]) {
|
||||
linksAcc.push(manageLinksById[linkId]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<PluginSetup, PluginStart, SetupPlugins, S
|
|||
private telemetry: TelemetryService;
|
||||
|
||||
readonly experimentalFeatures: ExperimentalFeatures;
|
||||
private isSidebarEnabled$: BehaviorSubject<boolean>;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.config = this.initializerContext.config.get<SecuritySolutionUiConfigType>();
|
||||
|
@ -93,7 +95,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
this.kibanaVersion = initializerContext.env.packageInfo.version;
|
||||
this.kibanaBranch = initializerContext.env.packageInfo.branch;
|
||||
this.prebuiltRulesPackageVersion = this.config.prebuiltRulesPackageVersion;
|
||||
|
||||
this.isSidebarEnabled$ = new BehaviorSubject<boolean>(true);
|
||||
this.telemetry = new TelemetryService();
|
||||
}
|
||||
private appUpdater$ = new Subject<AppUpdater>();
|
||||
|
@ -168,6 +170,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
getPluginWrapper: () => SecuritySolutionTemplateWrapper,
|
||||
},
|
||||
savedObjectsManagement: startPluginsDeps.savedObjectsManagement,
|
||||
isSidebarEnabled$: this.isSidebarEnabled$,
|
||||
telemetry: this.telemetry.start(),
|
||||
};
|
||||
return services;
|
||||
|
@ -245,7 +248,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: StartPlugins) {
|
||||
public start(core: CoreStart, plugins: StartPlugins): PluginStart {
|
||||
KibanaServices.init({
|
||||
...core,
|
||||
...plugins,
|
||||
|
@ -306,7 +309,11 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
// Not using await to prevent blocking start execution
|
||||
this.registerAppLinks(core, plugins);
|
||||
|
||||
return {};
|
||||
return {
|
||||
navLinks$,
|
||||
setIsSidebarEnabled: (isSidebarEnabled: boolean) =>
|
||||
this.isSidebarEnabled$.next(isSidebarEnabled),
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {
|
||||
|
|
|
@ -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<boolean>;
|
||||
telemetry: TelemetryClientStart;
|
||||
};
|
||||
|
||||
export interface PluginSetup {
|
||||
resolver: () => Promise<ResolverPluginSetup>;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface PluginStart {}
|
||||
|
||||
export interface PluginStart {
|
||||
navLinks$: Observable<NavigationLink[]>;
|
||||
setIsSidebarEnabled: (isSidebarEnabled: boolean) => void;
|
||||
}
|
||||
|
||||
export interface AppObservableLibs {
|
||||
kibana: CoreStart;
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
],
|
||||
"requiredPlugins": [
|
||||
"kibanaReact",
|
||||
"management",
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ServerlessPluginSetup, ServerlessPluginStart> {
|
||||
export class ServerlessPlugin
|
||||
implements
|
||||
Plugin<
|
||||
ServerlessPluginSetup,
|
||||
ServerlessPluginStart,
|
||||
ServerlessPluginSetupDependencies,
|
||||
ServerlessPluginStartDependencies
|
||||
>
|
||||
{
|
||||
private readonly config: ServerlessConfig;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.config = this.initializerContext.config.get<ServerlessConfig>();
|
||||
}
|
||||
|
||||
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<ServerlessPluginSetup, Serverles
|
|||
}
|
||||
|
||||
core.chrome.setChromeStyle('project');
|
||||
management.setIsSidebarEnabled(false);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -5,8 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ServerlessPluginSetup {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ServerlessPluginStart {}
|
||||
|
||||
export interface ServerlessPluginSetupDependencies {
|
||||
management: ManagementSetup;
|
||||
}
|
||||
|
||||
export interface ServerlessPluginStartDependencies {
|
||||
management: ManagementStart;
|
||||
}
|
||||
|
|
|
@ -14,11 +14,12 @@
|
|||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/core",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/utils",
|
||||
"@kbn/management-plugin",
|
||||
"@kbn/serverless-project-switcher",
|
||||
"@kbn/serverless-types",
|
||||
"@kbn/utils",
|
||||
]
|
||||
}
|
||||
|
|
2
x-pack/plugins/serverless_security/.gitignore
vendored
Normal file
2
x-pack/plugins/serverless_security/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/build
|
||||
/target
|
3
x-pack/plugins/serverless_security/README.mdx
Executable file
3
x-pack/plugins/serverless_security/README.mdx
Executable file
|
@ -0,0 +1,3 @@
|
|||
# Serverless Security project plugin
|
||||
|
||||
This plugin contains configuration and code used to create a Serverless Security project. It leverages universal configuration and other APIs in the [`serverless`](../serverless/README.mdx) plugin to configure Kibana.
|
9
x-pack/plugins/serverless_security/common/index.ts
Normal file
9
x-pack/plugins/serverless_security/common/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const PLUGIN_ID = 'serverlessSecurity';
|
||||
export const PLUGIN_NAME = 'serverlessSecurity';
|
24
x-pack/plugins/serverless_security/kibana.jsonc
Normal file
24
x-pack/plugins/serverless_security/kibana.jsonc
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/serverless-security",
|
||||
"owner": "@elastic/appex-sharedux",
|
||||
"description": "Serverless customizations for security.",
|
||||
"plugin": {
|
||||
"id": "serverlessSecurity",
|
||||
"server": true,
|
||||
"browser": true,
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"serverless",
|
||||
"security"
|
||||
],
|
||||
"requiredPlugins": [
|
||||
"serverless",
|
||||
"security",
|
||||
"securitySolution",
|
||||
"kibanaReact"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": []
|
||||
}
|
||||
}
|
11
x-pack/plugins/serverless_security/package.json
Normal file
11
x-pack/plugins/serverless_security/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "@kbn/serverless-security",
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn plugin-helpers build",
|
||||
"plugin-helpers": "node ../../../scripts/plugin_helpers",
|
||||
"kbn": "node ../../../scripts/kbn"
|
||||
}
|
||||
}
|
16
x-pack/plugins/serverless_security/public/index.ts
Normal file
16
x-pack/plugins/serverless_security/public/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { ServerlessSecurityPlugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin() {
|
||||
return new ServerlessSecurityPlugin();
|
||||
}
|
||||
|
||||
export type { ServerlessSecurityPluginSetup, ServerlessSecurityPluginStart } from './types';
|
41
x-pack/plugins/serverless_security/public/plugin.ts
Normal file
41
x-pack/plugins/serverless_security/public/plugin.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import {
|
||||
ServerlessSecurityPluginSetup,
|
||||
ServerlessSecurityPluginStart,
|
||||
ServerlessSecurityPluginSetupDependencies,
|
||||
ServerlessSecurityPluginStartDependencies,
|
||||
} from './types';
|
||||
|
||||
export class ServerlessSecurityPlugin
|
||||
implements
|
||||
Plugin<
|
||||
ServerlessSecurityPluginSetup,
|
||||
ServerlessSecurityPluginStart,
|
||||
ServerlessSecurityPluginSetupDependencies,
|
||||
ServerlessSecurityPluginStartDependencies
|
||||
>
|
||||
{
|
||||
public setup(
|
||||
_core: CoreSetup,
|
||||
_setupDeps: ServerlessSecurityPluginSetupDependencies
|
||||
): ServerlessSecurityPluginSetup {
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(
|
||||
_core: CoreStart,
|
||||
{ securitySolution }: ServerlessSecurityPluginStartDependencies
|
||||
): ServerlessSecurityPluginStart {
|
||||
securitySolution.setIsSidebarEnabled(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
31
x-pack/plugins/serverless_security/public/types.ts
Normal file
31
x-pack/plugins/serverless_security/public/types.ts
Normal file
|
@ -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;
|
||||
}
|
23
x-pack/plugins/serverless_security/server/config.ts
Normal file
23
x-pack/plugins/serverless_security/server/config.ts
Normal file
|
@ -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<typeof configSchema>;
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||||
schema: configSchema,
|
||||
};
|
||||
|
||||
export type ServerlessSecurityConfig = TypeOf<typeof configSchema>;
|
20
x-pack/plugins/serverless_security/server/index.ts
Normal file
20
x-pack/plugins/serverless_security/server/index.ts
Normal file
|
@ -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';
|
37
x-pack/plugins/serverless_security/server/plugin.ts
Normal file
37
x-pack/plugins/serverless_security/server/plugin.ts
Normal file
|
@ -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() {}
|
||||
}
|
27
x-pack/plugins/serverless_security/server/types.ts
Normal file
27
x-pack/plugins/serverless_security/server/types.ts
Normal file
|
@ -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;
|
||||
}
|
24
x-pack/plugins/serverless_security/tsconfig.json
Normal file
24
x-pack/plugins/serverless_security/tsconfig.json
Normal file
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -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 ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue