From 6b3705bcd234d7c2359b47d1a47099c2e45210d6 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Thu, 16 Mar 2023 16:06:29 -0400 Subject: [PATCH] Consolidate commits from elastic/kibana#152172 into a new feature branch --- .github/CODEOWNERS | 4 + config/serverless.es.yml | 10 ++ config/serverless.oblt.yml | 9 ++ config/serverless.security.yml | 10 ++ config/serverless.yml | 13 ++ docs/developer/plugin-list.asciidoc | 16 ++ package.json | 5 + .../src/chrome_service.test.ts | 2 + .../src/chrome_service.tsx | 115 ++++++++++---- .../src/ui/index.ts | 1 + .../src/ui/solution/header.tsx | 90 +++++++++++ .../src/ui/solution/index.ts | 9 ++ .../src/ui/solution/side_navigation.tsx | 54 +++++++ .../tsconfig.json | 5 +- .../src/chrome_service.mock.ts | 3 + .../core/chrome/core-chrome-browser/index.ts | 27 ++-- .../core-chrome-browser/src/contracts.ts | 6 +- .../chrome/core-chrome-browser/src/index.ts | 2 +- .../chrome/core-chrome-browser/src/types.ts | 3 + packages/kbn-optimizer/limits.yml | 4 + src/cli/serve/serve.js | 44 +++++- src/core/public/styles/rendering/_base.scss | 3 + .../management_app/management_app.tsx | 28 ++-- src/plugins/management/public/mocks/index.ts | 1 + src/plugins/management/public/plugin.ts | 7 + src/plugins/management/public/types.ts | 1 + tsconfig.base.json | 8 + x-pack/.i18nrc.json | 69 ++++++--- x-pack/plugins/apm/server/index.ts | 1 + x-pack/plugins/canvas/server/index.ts | 8 + .../server/config.ts | 5 + .../__mocks__/kea_logic/kibana_logic.mock.ts | 1 + .../public/applications/index.test.tsx | 1 + .../public/applications/index.tsx | 13 +- .../shared/kibana/kibana_logic.ts | 3 + .../public/applications/shared/layout/nav.tsx | 4 +- .../shared/layout/page_template.tsx | 6 +- .../plugins/enterprise_search/public/index.ts | 2 + .../enterprise_search/public/plugin.ts | 14 +- .../plugins/enterprise_search/server/index.ts | 1 + x-pack/plugins/fleet/common/authz.ts | 2 +- x-pack/plugins/fleet/server/config.ts | 1 + .../server/config.ts | 5 + .../license_management/server/config.ts | 5 + .../page_template/lazy_page_template.tsx | 28 +++- .../page_template/page_template.test.tsx | 1 + .../shared/page_template/page_template.tsx | 6 +- x-pack/plugins/observability/public/plugin.ts | 2 + .../public/services/navigation_registry.ts | 10 +- x-pack/plugins/observability/server/index.ts | 1 + .../plugins/remote_clusters/server/config.ts | 5 + x-pack/plugins/rollup/server/config.ts | 5 + x-pack/plugins/security/public/config.ts | 1 + .../management/management_service.test.ts | 3 + .../nav_control/nav_control_service.tsx | 7 +- x-pack/plugins/security/public/plugin.tsx | 1 + x-pack/plugins/security/server/config.test.ts | 6 + x-pack/plugins/security/server/config.ts | 2 + x-pack/plugins/security/server/index.ts | 1 + .../plugins/security_solution/common/index.ts | 1 + .../public/app/deep_links/index.ts | 4 +- .../components/navigation/nav_links.test.ts | 95 ------------ .../security_side_nav.test.tsx | 2 +- .../security_side_nav/security_side_nav.tsx | 4 +- .../common/components/navigation/types.ts | 1 + .../use_primary_navigation.tsx | 10 ++ .../common/lib/kibana/kibana_react.mock.ts | 2 + .../public/common/links/links.ts | 96 +++++------- .../public/common/links/nav_links.test.ts | 57 +++++++ .../navigation => links}/nav_links.ts | 44 +++--- .../public/common/links/types.ts | 16 ++ .../plugins/security_solution/public/index.ts | 4 +- .../landing_pages/pages/dashboards.test.tsx | 8 +- .../public/landing_pages/pages/dashboards.tsx | 4 +- .../public/landing_pages/pages/explore.tsx | 4 +- .../landing_pages/pages/manage.test.tsx | 8 +- .../public/landing_pages/pages/manage.tsx | 10 +- .../security_solution/public/plugin.tsx | 16 +- .../plugins/security_solution/public/types.ts | 11 +- .../security_solution/server/config.mock.ts | 1 + .../security_solution/server/config.ts | 1 + x-pack/plugins/serverless/README.md | 19 +++ x-pack/plugins/serverless/common/index.ts | 11 ++ x-pack/plugins/serverless/kibana.jsonc | 21 +++ x-pack/plugins/serverless/package.json | 11 ++ .../serverless/public/components/index.ts | 8 + .../public/components/switcher/index.tsx | 96 ++++++++++++ .../public/components/switcher/item.tsx | 20 +++ .../public/components/switcher/loader.tsx | 20 +++ .../public/components/switcher/logo.tsx | 31 ++++ x-pack/plugins/serverless/public/index.ts | 14 ++ x-pack/plugins/serverless/public/plugin.tsx | 54 +++++++ x-pack/plugins/serverless/public/types.ts | 15 ++ x-pack/plugins/serverless/server/config.ts | 23 +++ x-pack/plugins/serverless/server/index.ts | 19 +++ x-pack/plugins/serverless/server/plugin.ts | 63 ++++++++ x-pack/plugins/serverless/server/types.ts | 12 ++ x-pack/plugins/serverless/tsconfig.json | 24 +++ .../serverless_observability/.gitignore | 2 + .../serverless_observability/README.md | 3 + .../serverless_observability/common/index.ts | 9 ++ .../serverless_observability/kibana.jsonc | 22 +++ .../serverless_observability/package.json | 11 ++ .../serverless_observability/public/index.ts | 19 +++ .../public/plugin.tsx | 39 +++++ .../serverless_observability/public/types.ts | 28 ++++ .../serverless_observability/server/config.ts | 23 +++ .../serverless_observability/server/index.ts | 23 +++ .../serverless_observability/server/plugin.ts | 26 ++++ .../serverless_observability/server/types.ts | 11 ++ .../serverless_observability/tsconfig.json | 23 +++ x-pack/plugins/serverless_search/.gitignore | 2 + x-pack/plugins/serverless_search/README.md | 3 + .../plugins/serverless_search/common/index.ts | 9 ++ x-pack/plugins/serverless_search/kibana.jsonc | 23 +++ x-pack/plugins/serverless_search/package.json | 11 ++ .../plugins/serverless_search/public/index.ts | 16 ++ .../serverless_search/public/layout/nav.tsx | 54 +++++++ .../serverless_search/public/plugin.tsx | 45 ++++++ .../plugins/serverless_search/public/types.ts | 31 ++++ .../serverless_search/server/config.ts | 23 +++ .../plugins/serverless_search/server/index.ts | 20 +++ .../serverless_search/server/plugin.ts | 26 ++++ .../plugins/serverless_search/server/types.ts | 11 ++ .../plugins/serverless_search/tsconfig.json | 24 +++ x-pack/plugins/serverless_security/.gitignore | 2 + x-pack/plugins/serverless_security/README.md | 3 + .../serverless_security/common/index.ts | 9 ++ .../plugins/serverless_security/kibana.jsonc | 24 +++ .../plugins/serverless_security/package.json | 11 ++ .../components/side_navigation/index.ts | 7 + .../side_navigation/side_navigation.tsx | 44 ++++++ .../public/hooks/use_link_props.ts | 65 ++++++++ .../public/hooks/use_side_nav_items.ts | 144 ++++++++++++++++++ .../serverless_security/public/index.ts | 16 ++ .../serverless_security/public/plugin.tsx | 52 +++++++ .../serverless_security/public/services.tsx | 28 ++++ .../serverless_security/public/types.ts | 31 ++++ .../serverless_security/server/config.ts | 23 +++ .../serverless_security/server/index.ts | 20 +++ .../serverless_security/server/plugin.ts | 37 +++++ .../serverless_security/server/types.ts | 27 ++++ .../plugins/serverless_security/tsconfig.json | 27 ++++ .../plugins/snapshot_restore/server/config.ts | 5 + x-pack/plugins/synthetics/common/config.ts | 1 + x-pack/plugins/synthetics/kibana.jsonc | 6 +- .../lib/adapters/framework/adapter_types.ts | 3 +- .../get_service_locations.test.ts | 3 + .../synthetics_private_location.ts | 2 +- .../synthetics_service.test.ts | 3 +- .../upgrade_assistant/server/config.ts | 8 + x-pack/plugins/watcher/server/index.ts | 8 + yarn.lock | 16 ++ 153 files changed, 2412 insertions(+), 310 deletions(-) create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/solution/header.tsx create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/solution/index.ts create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/solution/side_navigation.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/links/nav_links.test.ts rename x-pack/plugins/security_solution/public/common/{components/navigation => links}/nav_links.ts (52%) create mode 100755 x-pack/plugins/serverless/README.md create mode 100644 x-pack/plugins/serverless/common/index.ts create mode 100644 x-pack/plugins/serverless/kibana.jsonc create mode 100644 x-pack/plugins/serverless/package.json create mode 100644 x-pack/plugins/serverless/public/components/index.ts create mode 100644 x-pack/plugins/serverless/public/components/switcher/index.tsx create mode 100644 x-pack/plugins/serverless/public/components/switcher/item.tsx create mode 100644 x-pack/plugins/serverless/public/components/switcher/loader.tsx create mode 100644 x-pack/plugins/serverless/public/components/switcher/logo.tsx create mode 100644 x-pack/plugins/serverless/public/index.ts create mode 100644 x-pack/plugins/serverless/public/plugin.tsx create mode 100644 x-pack/plugins/serverless/public/types.ts create mode 100644 x-pack/plugins/serverless/server/config.ts create mode 100644 x-pack/plugins/serverless/server/index.ts create mode 100644 x-pack/plugins/serverless/server/plugin.ts create mode 100644 x-pack/plugins/serverless/server/types.ts create mode 100644 x-pack/plugins/serverless/tsconfig.json create mode 100644 x-pack/plugins/serverless_observability/.gitignore create mode 100755 x-pack/plugins/serverless_observability/README.md create mode 100644 x-pack/plugins/serverless_observability/common/index.ts create mode 100644 x-pack/plugins/serverless_observability/kibana.jsonc create mode 100644 x-pack/plugins/serverless_observability/package.json create mode 100644 x-pack/plugins/serverless_observability/public/index.ts create mode 100644 x-pack/plugins/serverless_observability/public/plugin.tsx create mode 100644 x-pack/plugins/serverless_observability/public/types.ts create mode 100644 x-pack/plugins/serverless_observability/server/config.ts create mode 100644 x-pack/plugins/serverless_observability/server/index.ts create mode 100644 x-pack/plugins/serverless_observability/server/plugin.ts create mode 100644 x-pack/plugins/serverless_observability/server/types.ts create mode 100644 x-pack/plugins/serverless_observability/tsconfig.json create mode 100644 x-pack/plugins/serverless_search/.gitignore create mode 100755 x-pack/plugins/serverless_search/README.md create mode 100644 x-pack/plugins/serverless_search/common/index.ts create mode 100644 x-pack/plugins/serverless_search/kibana.jsonc create mode 100644 x-pack/plugins/serverless_search/package.json create mode 100644 x-pack/plugins/serverless_search/public/index.ts create mode 100644 x-pack/plugins/serverless_search/public/layout/nav.tsx create mode 100644 x-pack/plugins/serverless_search/public/plugin.tsx create mode 100644 x-pack/plugins/serverless_search/public/types.ts create mode 100644 x-pack/plugins/serverless_search/server/config.ts create mode 100644 x-pack/plugins/serverless_search/server/index.ts create mode 100644 x-pack/plugins/serverless_search/server/plugin.ts create mode 100644 x-pack/plugins/serverless_search/server/types.ts create mode 100644 x-pack/plugins/serverless_search/tsconfig.json create mode 100644 x-pack/plugins/serverless_security/.gitignore create mode 100755 x-pack/plugins/serverless_security/README.md create mode 100644 x-pack/plugins/serverless_security/common/index.ts create mode 100644 x-pack/plugins/serverless_security/kibana.jsonc create mode 100644 x-pack/plugins/serverless_security/package.json create mode 100644 x-pack/plugins/serverless_security/public/components/side_navigation/index.ts create mode 100644 x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.tsx create mode 100644 x-pack/plugins/serverless_security/public/hooks/use_link_props.ts create mode 100644 x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts create mode 100644 x-pack/plugins/serverless_security/public/index.ts create mode 100644 x-pack/plugins/serverless_security/public/plugin.tsx create mode 100644 x-pack/plugins/serverless_security/public/services.tsx create mode 100644 x-pack/plugins/serverless_security/public/types.ts create mode 100644 x-pack/plugins/serverless_security/server/config.ts create mode 100644 x-pack/plugins/serverless_security/server/index.ts create mode 100644 x-pack/plugins/serverless_security/server/plugin.ts create mode 100644 x-pack/plugins/serverless_security/server/types.ts create mode 100644 x-pack/plugins/serverless_security/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 914d4e78e1d04..94e55a675ebd7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -551,6 +551,10 @@ packages/kbn-securitysolution-t-grid @elastic/security-solution-platform packages/kbn-securitysolution-utils @elastic/security-solution-platform packages/kbn-server-http-tools @elastic/kibana-core packages/kbn-server-route-repository @elastic/apm-ui +x-pack/plugins/serverless @elastic/appex-sharedux +x-pack/plugins/serverless_observability @elastic/appex-sharedux +x-pack/plugins/serverless_search @elastic/appex-sharedux +x-pack/plugins/serverless_security @elastic/appex-sharedux test/plugin_functional/plugins/session_notifications @elastic/kibana-core x-pack/plugins/session_view @elastic/awp-viz packages/kbn-set-map @elastic/kibana-operations diff --git a/config/serverless.es.yml b/config/serverless.es.yml index e69de29bb2d1d..73e90be302b27 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -0,0 +1,10 @@ +xpack.apm.enabled: false +xpack.canvas.enabled: false +xpack.reporting.enabled: false +xpack.uptime.enabled: false +xpack.watcher.enabled: false + +enterpriseSearch.enabled: true +xpack.serverless.search.enabled: true + +uiSettings.overrides.defaultRoute: /app/enterprise_search/content/search_indices diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index e69de29bb2d1d..6023b65b82e82 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -0,0 +1,9 @@ +enterpriseSearch.enabled: false +xpack.canvas.enabled: false +xpack.cloudSecurityPosture.enabled: false +xpack.reporting.enabled: false +xpack.securitySolution.enabled: false + +xpack.serverless.observability.enabled: true + +uiSettings.overrides.defaultRoute: /app/observability/overview diff --git a/config/serverless.security.yml b/config/serverless.security.yml index e69de29bb2d1d..820f0488566fd 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -0,0 +1,10 @@ +enterpriseSearch.enabled: false +xpack.apm.enabled: false +xpack.canvas.enabled: false +xpack.observability.enabled: false +xpack.reporting.enabled: false +xpack.uptime.enabled: false + +xpack.serverless.security.enabled: true + +uiSettings.overrides.defaultRoute: /app/security/get_started diff --git a/config/serverless.yml b/config/serverless.yml index 6eabe0049fbfd..0fc0ac4d627d2 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -1 +1,14 @@ +newsfeed.enabled: false +xpack.security.showNavLinks: false +xpack.serverless.plugin.enabled: true xpack.fleet.enableExperimental: ['fleetServerStandalone'] + +# Management team plugins +xpack.upgrade_assistant.enabled: false +xpack.rollup.enabled: false +xpack.watcher.enabled: false +xpack.ccr.enabled: false +xpack.ilm.enabled: false +xpack.remote_clusters.enabled: false +xpack.snapshot_restore.enabled: false +xpack.license_management.enabled: false diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b9df0e487b2c0..d9a7c9a05bf22 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -689,6 +689,22 @@ Kibana. |Welcome to the Kibana Security Solution plugin! This README will go over getting started with development and testing. +|{kib-repo}blob/{branch}/x-pack/plugins/serverless/README.md[serverless] +|A Kibana plugin + + +|{kib-repo}blob/{branch}/x-pack/plugins/serverless_observability/README.md[serverlessObservability] +|A witty, fitting description to come. + + +|{kib-repo}blob/{branch}/x-pack/plugins/serverless_search/README.md[serverlessSearch] +|A witty, fitting description to come. + + +|{kib-repo}blob/{branch}/x-pack/plugins/serverless_security/README.md[serverlessSecurity] +|A witty, fitting description to come. + + |{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 967ffe0dd015c..3a428815aa099 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "lint:es": "node scripts/eslint", "lint:style": "node scripts/stylelint", "makelogs": "node scripts/makelogs", + "serverless": "node scripts/kibana --dev --serverless", "serverless-es": "node scripts/kibana --dev --serverless=es", "serverless-oblt": "node scripts/kibana --dev --serverless=oblt", "serverless-security": "node scripts/kibana --dev --serverless=security", @@ -553,6 +554,10 @@ "@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils", "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", + "@kbn/serverless": "link:x-pack/plugins/serverless", + "@kbn/serverless-observability": "link:x-pack/plugins/serverless_observability", + "@kbn/serverless-search": "link:x-pack/plugins/serverless_search", + "@kbn/serverless-security": "link:x-pack/plugins/serverless_security", "@kbn/session-notifications-plugin": "link:test/plugin_functional/plugins/session_notifications", "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", "@kbn/set-map": "link:packages/kbn-set-map", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts index 37b1b9a2eab7d..0087c5d019f98 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.ts @@ -126,6 +126,7 @@ describe('start', () => { Array [ Array [ "kbnBody", + "kbnBody--classicLayout", "kbnBody--noHeaderBanner", "kbnBody--chromeHidden", "kbnVersion-1-2-3", @@ -143,6 +144,7 @@ describe('start', () => { Array [ Array [ "kbnBody", + "kbnBody--classicLayout", "kbnBody--noHeaderBanner", "kbnBody--chromeHidden", "kbnVersion-8-0-0", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 4e0762aee8620..99ad7869f429a 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -26,6 +26,7 @@ import type { ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, ChromeUserBanner, + ChromeStyle, } from '@kbn/core-chrome-browser'; import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser'; import { KIBANA_ASK_ELASTIC_LINK } from './constants'; @@ -33,7 +34,7 @@ import { DocTitleService } from './doc_title'; import { NavControlsService } from './nav_controls'; import { NavLinksService } from './nav_links'; import { RecentlyAccessedService } from './recently_accessed'; -import { Header } from './ui'; +import { Header, SolutionHeader } from './ui'; import type { InternalChromeStart } from './types'; const IS_LOCKED_KEY = 'core.chrome.isLocked'; @@ -119,6 +120,8 @@ export class ChromeService { const customNavLink$ = new BehaviorSubject(undefined); const helpSupportUrl$ = new BehaviorSubject(KIBANA_ASK_ELASTIC_LINK); const isNavDrawerLocked$ = new BehaviorSubject(localStorage.getItem(IS_LOCKED_KEY) === 'true'); + const chromeStyle$ = new BehaviorSubject('classic'); + const solutionNavigation$ = new BehaviorSubject(undefined); const getKbnVersionClass = () => { // we assume that the version is valid and has the form 'X.X.X' @@ -135,6 +138,9 @@ export class ChromeService { map(([headerBanner, isVisible]) => { return [ 'kbnBody', + chromeStyle$.getValue() === 'classic' + ? 'kbnBody--classicLayout' + : 'kbnBody--projectLayout', headerBanner ? 'kbnBody--hasHeaderBanner' : 'kbnBody--noHeaderBanner', isVisible ? 'kbnBody--chromeVisible' : 'kbnBody--chromeHidden', getKbnVersionClass(), @@ -163,6 +169,16 @@ export class ChromeService { const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$)); + const setChromeStyle = (style: ChromeStyle) => { + chromeStyle$.next(style); + }; + + const setSolutionNavigation = (navigation: JSX.Element) => { + solutionNavigation$.next(navigation); + }; + + const getChromeStyle$ = chromeStyle$.pipe(takeUntil(this.stop$)); + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -203,41 +219,75 @@ export class ChromeService { }); } + const getHeaderComponent = () => { + const Component = ({ + style$, + navigation$, + }: { + style$: typeof chromeStyle$; + navigation$: typeof solutionNavigation$; + }) => { + if (style$.getValue() === 'solution') { + const navigation = navigation$.getValue(); + if (navigation) { + return ( + + ); + } + } + + return ( +
+ ); + }; + return ; + }; + return { navControls, navLinks, recentlyAccessed, docTitle, - - getHeaderComponent: () => ( -
- ), + getHeaderComponent, getIsVisible$: () => this.isVisible$, @@ -302,6 +352,9 @@ export class ChromeService { }, getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)), + setChromeStyle, + getChromeStyle$: () => getChromeStyle$, + setSolutionNavigation, }; } diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts b/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts index 5afd3e0f587bb..0fd5240eac29d 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/index.ts @@ -7,5 +7,6 @@ */ export { Header } from './header'; +export { SolutionHeader } from './solution'; export { LoadingIndicator } from './loading_indicator'; export type { NavType } from './header'; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/header.tsx new file mode 100644 index 0000000000000..60194dd3b8295 --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/header.tsx @@ -0,0 +1,90 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { Router } from 'react-router-dom'; +import { EuiHeader, EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem } from '@elastic/eui'; +import { + ChromeBreadcrumb, + ChromeGlobalHelpExtensionMenuLink, + ChromeHelpExtension, + ChromeNavControl, +} from '@kbn/core-chrome-browser/src'; +import { Observable } from 'rxjs'; +import { MountPoint } from '@kbn/core-mount-utils-browser'; +import { InternalApplicationStart } from '@kbn/core-application-browser-internal'; +import { HeaderBreadcrumbs } from '../header/header_breadcrumbs'; +import { HeaderActionMenu } from '../header/header_action_menu'; +import { HeaderHelpMenu } from '../header/header_help_menu'; +import { HeaderNavControls } from '../header/header_nav_controls'; +import { SolutionSideNavigation } from './side_navigation'; + +interface Props { + breadcrumbs$: Observable; + actionMenu$: Observable; + kibanaDocLink: string; + globalHelpExtensionMenuLinks$: Observable; + helpExtension$: Observable; + helpSupportUrl$: Observable; + kibanaVersion: string; + application: InternalApplicationStart; + navigation: JSX.Element; + navControlsRight$: Observable; +} + +export const SolutionHeader = ({ + application, + kibanaDocLink, + kibanaVersion, + navigation, + ...observables +}: Props) => { + const renderLogo = () => ( + e.preventDefault()} + aria-label="Go to home page" + /> + ); + + return ( + <> + + + {renderLogo()} + + + + + + + + + + + + + + + + + + + {navigation} + + + ); +}; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/index.ts b/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/index.ts new file mode 100644 index 0000000000000..5c116bc6b30c8 --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/index.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { SolutionHeader } from './header'; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/side_navigation.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/side_navigation.tsx new file mode 100644 index 0000000000000..4ed9631567e6f --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/solution/side_navigation.tsx @@ -0,0 +1,54 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { EuiButtonIcon, EuiCollapsibleNav, EuiThemeProvider, useEuiTheme } from '@elastic/eui'; + +const LOCAL_STORAGE_IS_OPEN_KEY = 'SOLUTION_SIDE_NAVIGATION_OPEN' as const; + +export const SolutionSideNavigation: React.FC = ({ children }) => { + const { euiTheme, colorMode } = useEuiTheme(); + + const [isOpen, setIsOpen] = useLocalStorage(LOCAL_STORAGE_IS_OPEN_KEY, true); + + const toggleOpen = useCallback(() => { + setIsOpen(!isOpen); + }, [isOpen, setIsOpen]); + + return ( + + + + + } + > + {isOpen && children} + + + ); +}; diff --git a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json index 4d4d6cad3bc21..cd27209bef12c 100644 --- a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json +++ b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json @@ -5,7 +5,10 @@ "types": [ "jest", "node", - "react" + "react", + "@kbn/ambient-ui-types", + "@kbn/ambient-storybook-types", + "@emotion/react/types/css-prop" ] }, "include": [ diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 2f5c4deb1f38d..e33eca66322f8 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -61,6 +61,9 @@ const createStartContractMock = () => { setHeaderBanner: jest.fn(), hasHeaderBanner$: jest.fn(), getBodyClasses$: jest.fn(), + getChromeStyle$: jest.fn(), + setChromeStyle: jest.fn(), + setSolutionNavigation: jest.fn(), }; startContract.navLinks.getAll.mockReturnValue([]); startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false)); diff --git a/packages/core/chrome/core-chrome-browser/index.ts b/packages/core/chrome/core-chrome-browser/index.ts index 3fbef34126a4a..1d2dca4c957bc 100644 --- a/packages/core/chrome/core-chrome-browser/index.ts +++ b/packages/core/chrome/core-chrome-browser/index.ts @@ -7,25 +7,26 @@ */ export type { - ChromeUserBanner, + ChromeBadge, ChromeBreadcrumb, + ChromeBreadcrumbsAppendExtension, + ChromeDocTitle, + ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, - ChromeHelpExtensionMenuLink, ChromeHelpExtensionLinkBase, + ChromeHelpExtensionMenuCustomLink, + ChromeHelpExtensionMenuDiscussLink, + ChromeHelpExtensionMenuDocumentationLink, + ChromeHelpExtensionMenuGitHubLink, + ChromeHelpExtensionMenuLink, ChromeHelpMenuActions, - ChromeNavLink, - ChromeBreadcrumbsAppendExtension, - ChromeNavLinks, ChromeNavControl, ChromeNavControls, - ChromeBadge, - ChromeHelpExtensionMenuGitHubLink, - ChromeHelpExtensionMenuDocumentationLink, - ChromeHelpExtensionMenuDiscussLink, - ChromeHelpExtensionMenuCustomLink, - ChromeGlobalHelpExtensionMenuLink, - ChromeDocTitle, - ChromeStart, + ChromeNavLink, + ChromeNavLinks, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, + ChromeStart, + ChromeStyle, + ChromeUserBanner, } from './src'; diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts index a81d9c3c6338f..2a966e367d6e4 100644 --- a/packages/core/chrome/core-chrome-browser/src/contracts.ts +++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts @@ -13,7 +13,7 @@ import type { ChromeDocTitle } from './doc_title'; import type { ChromeNavControls } from './nav_controls'; import type { ChromeHelpExtension } from './help_extension'; import type { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from './breadcrumb'; -import type { ChromeBadge, ChromeUserBanner } from './types'; +import type { ChromeBadge, ChromeStyle, ChromeUserBanner } from './types'; import { ChromeGlobalHelpExtensionMenuLink } from './help_extension'; /** @@ -150,4 +150,8 @@ export interface ChromeStart { * Get an observable of the current header banner presence state. */ hasHeaderBanner$(): Observable; + + setChromeStyle(style: ChromeStyle): void; + getChromeStyle$(): Observable; + setSolutionNavigation(solutionNavigation: JSX.Element): void; } diff --git a/packages/core/chrome/core-chrome-browser/src/index.ts b/packages/core/chrome/core-chrome-browser/src/index.ts index 716af097fded7..89ba12d616d0e 100644 --- a/packages/core/chrome/core-chrome-browser/src/index.ts +++ b/packages/core/chrome/core-chrome-browser/src/index.ts @@ -26,4 +26,4 @@ export type { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, } from './recently_accessed'; -export type { ChromeBadge, ChromeUserBanner } from './types'; +export type { ChromeBadge, ChromeUserBanner, ChromeStyle } from './types'; diff --git a/packages/core/chrome/core-chrome-browser/src/types.ts b/packages/core/chrome/core-chrome-browser/src/types.ts index 81b8c32a1a04c..40f29a073137d 100644 --- a/packages/core/chrome/core-chrome-browser/src/types.ts +++ b/packages/core/chrome/core-chrome-browser/src/types.ts @@ -20,3 +20,6 @@ export interface ChromeBadge { export interface ChromeUserBanner { content: MountPoint; } + +/** @public */ +export type ChromeStyle = 'classic' | 'solution'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index ad2fd0490197e..a053306e3da50 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -112,6 +112,10 @@ pageLoadAssetSize: searchprofiler: 67080 security: 65433 securitySolution: 66738 + serverless: 16573 + serverlessObservability: 16582 + serverlessSearch: 17548 + serverlessSecurity: 16556 sessionView: 77750 share: 71239 snapshotRestore: 79032 diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 4a875d6955428..bd5d0f47e7e11 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -8,7 +8,7 @@ import { set as lodashSet } from '@kbn/safer-lodash-set'; import _ from 'lodash'; -import { statSync } from 'fs'; +import { statSync, copyFileSync, existsSync } from 'fs'; import { resolve } from 'path'; import url from 'url'; @@ -29,7 +29,7 @@ function getServerlessProjectMode(opts) { return null; } - if (VALID_SERVERLESS_PROJECT_MODE.includes(opts.serverless)) { + if (VALID_SERVERLESS_PROJECT_MODE.includes(opts.serverless) || opts.serverless === true) { return opts.serverless; } @@ -97,6 +97,33 @@ function maybeAddConfig(name, configs, method) { } } +/** + * @param {string} file + * @param {'es' | 'security' | 'oblt' | true} mode + * @param {string[]} configs + * @param {'push' | 'unshift'} method + */ +function maybeSetRecentConfig(file, mode, configs, method) { + const path = resolve(getConfigDirectory(), file); + + try { + if (mode === true && !existsSync(path)) { + const esPath = path.replace('recent', 'es'); + copyFileSync(esPath, path); + } else if (mode !== true) { + copyFileSync(path.replace('recent', mode), path); + } + + configs[method](path); + } catch (err) { + if (err.code === 'ENOENT') { + return; + } + + throw err; + } +} + /** * @returns {string[]} */ @@ -234,7 +261,14 @@ export default function (program) { '--run-examples', 'Adds plugin paths for all the Kibana example plugins and runs with no base path' ) - .option('--serverless ', 'Start Kibana in a serverless project mode'); + .option( + '--serverless', + 'Start Kibana in the most recent serverless project mode, (default is es)' + ) + .option( + '--serverless ', + 'Start Kibana in a specific serverless project mode' + ); } if (DEV_MODE_SUPPORTED) { @@ -264,7 +298,7 @@ export default function (program) { // we "unshift" .serverless. config so that it only overrides defaults if (serverlessMode) { maybeAddConfig(`serverless.yml`, configs, 'push'); - maybeAddConfig(`serverless.${serverlessMode}.yml`, configs, 'unshift'); + maybeSetRecentConfig('serverless.recent.yml', serverlessMode, configs, 'unshift'); } // .dev. configs are "pushed" so that they override all other config files @@ -272,7 +306,7 @@ export default function (program) { maybeAddConfig('kibana.dev.yml', configs, 'push'); if (serverlessMode) { maybeAddConfig(`serverless.dev.yml`, configs, 'push'); - maybeAddConfig(`serverless.${serverlessMode}.dev.yml`, configs, 'push'); + maybeSetRecentConfig('serverless.recent.dev.yml', serverlessMode, configs, 'unshift'); } } diff --git a/src/core/public/styles/rendering/_base.scss b/src/core/public/styles/rendering/_base.scss index 9d4296ca3b4ef..a9ece9955e6ca 100644 --- a/src/core/public/styles/rendering/_base.scss +++ b/src/core/public/styles/rendering/_base.scss @@ -75,6 +75,9 @@ &.kbnBody--chromeHidden { @include kbnAffordForHeader(0); } + &.kbnBody--projectLayout { + @include kbnAffordForHeader($euiHeaderHeightCompensation); + } &.kbnBody--chromeHidden.kbnBody--hasHeaderBanner { @include kbnAffordForHeader($kbnHeaderBannerHeight); } 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..f0a3eb57db139 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -34,6 +34,7 @@ export interface ManagementAppDependencies { sections: SectionsServiceStart; kibanaVersion: string; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; + getIsSidebarEnabled: () => boolean; } export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppProps) => { @@ -75,18 +76,21 @@ 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 = + dependencies.getIsSidebarEnabled() + ? { + 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..733c98876969a 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -39,6 +39,7 @@ const createSetupContract = (): ManagementSetup => ({ state: {}, })), }, + setIsSidebarEnabled: jest.fn(() => true), }); const createStartContract = (): ManagementStart => ({ diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 0cdb83d4b6793..39de850e2c72a 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -71,6 +71,8 @@ export class ManagementPlugin private hasAnyEnabledApps = true; + private isSidebarEnabled = true; + constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, { home, share }: ManagementSetupDependencies) { @@ -93,6 +95,7 @@ export class ManagementPlugin visible: () => this.hasAnyEnabledApps, }); } + const managementPlugin = this; core.application.register({ id: MANAGEMENT_APP_ID, @@ -111,6 +114,7 @@ export class ManagementPlugin sections: getSectionsServiceStartPrivate(), kibanaVersion, setBreadcrumbs: coreStart.chrome.setBreadcrumbs, + getIsSidebarEnabled: () => managementPlugin.isSidebarEnabled, }); }, }); @@ -118,6 +122,9 @@ export class ManagementPlugin return { sections: this.managementSections.setup(), locator, + setIsSidebarEnabled: (enabled: boolean) => { + this.isSidebarEnabled = enabled; + }, }; } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 5d8d963ea981e..b9896a0e38b21 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -16,6 +16,7 @@ import type { ManagementAppLocatorParams } from '../common/locator'; export interface ManagementSetup { sections: SectionsServiceSetup; locator: LocatorPublic; + setIsSidebarEnabled: (enabled: boolean) => void; } export interface DefinedSections { diff --git a/tsconfig.base.json b/tsconfig.base.json index e4ad01b21f5a7..a24a6dc309a2c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1096,6 +1096,14 @@ "@kbn/server-http-tools/*": ["packages/kbn-server-http-tools/*"], "@kbn/server-route-repository": ["packages/kbn-server-route-repository"], "@kbn/server-route-repository/*": ["packages/kbn-server-route-repository/*"], + "@kbn/serverless": ["x-pack/plugins/serverless"], + "@kbn/serverless/*": ["x-pack/plugins/serverless/*"], + "@kbn/serverless-observability": ["x-pack/plugins/serverless_observability"], + "@kbn/serverless-observability/*": ["x-pack/plugins/serverless_observability/*"], + "@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/session-notifications-plugin": ["test/plugin_functional/plugins/session_notifications"], "@kbn/session-notifications-plugin/*": ["test/plugin_functional/plugins/session_notifications/*"], "@kbn/session-view-plugin": ["x-pack/plugins/session_view"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index fbde4b1e73c7e..b577466ac07b7 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -25,9 +25,15 @@ "xpack.features": "plugins/features", "xpack.dataVisualizer": "plugins/data_visualizer", "xpack.fileUpload": "plugins/file_upload", - "xpack.globalSearch": ["plugins/global_search"], - "xpack.globalSearchBar": ["plugins/global_search_bar"], - "xpack.graph": ["plugins/graph"], + "xpack.globalSearch": [ + "plugins/global_search" + ], + "xpack.globalSearchBar": [ + "plugins/global_search_bar" + ], + "xpack.graph": [ + "plugins/graph" + ], "xpack.grokDebugger": "plugins/grokdebugger", "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", @@ -40,45 +46,74 @@ "xpack.licenseMgmt": "plugins/license_management", "xpack.licensing": "plugins/licensing", "xpack.lists": "plugins/lists", - "xpack.logstash": ["plugins/logstash"], + "xpack.logstash": [ + "plugins/logstash" + ], "xpack.main": "legacy/plugins/xpack_main", - "xpack.maps": ["plugins/maps"], - "xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"], - "xpack.ml": ["packages/ml/date_picker", "plugins/ml"], - "xpack.monitoring": ["plugins/monitoring"], - "xpack.osquery": ["plugins/osquery"], + "xpack.maps": [ + "plugins/maps" + ], + "xpack.aiops": [ + "packages/ml/aiops_components", + "plugins/aiops" + ], + "xpack.ml": [ + "packages/ml/date_picker", + "plugins/ml" + ], + "xpack.monitoring": [ + "plugins/monitoring" + ], + "xpack.osquery": [ + "plugins/osquery" + ], "xpack.painlessLab": "plugins/painless_lab", - "xpack.profiling": [ "plugins/profiling" ], + "xpack.profiling": [ + "plugins/profiling" + ], "xpack.remoteClusters": "plugins/remote_clusters", - "xpack.reporting": ["plugins/reporting"], - "xpack.rollupJobs": ["plugins/rollup"], + "xpack.reporting": [ + "plugins/reporting" + ], + "xpack.rollupJobs": [ + "plugins/rollup" + ], "xpack.runtimeFields": "plugins/runtime_fields", "xpack.screenshotting": "plugins/screenshotting", "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", + "xpack.serverless": "plugins/serverless", "xpack.securitySolution": "plugins/security_solution", "xpack.sessionView": "plugins/session_view", "xpack.snapshotRestore": "plugins/snapshot_restore", "xpack.spaces": "plugins/spaces", - "xpack.savedObjectsTagging": ["plugins/saved_objects_tagging"], + "xpack.savedObjectsTagging": [ + "plugins/saved_objects_tagging" + ], "xpack.taskManager": "legacy/plugins/task_manager", "xpack.timelines": "plugins/timelines", "xpack.transform": "plugins/transform", "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", - "xpack.synthetics": ["plugins/synthetics"], - "xpack.ux": ["plugins/ux"], + "xpack.synthetics": [ + "plugins/synthetics" + ], + "xpack.ux": [ + "plugins/ux" + ], "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown", "xpack.watcher": "plugins/watcher", "xpack.observability": "plugins/observability", "xpack.banners": "plugins/banners", "xpack.threatIntelligence": "plugins/threat_intelligence" }, - "exclude": ["examples"], + "exclude": [ + "examples" + ], "translations": [ "@kbn/translations-plugin/translations/zh-CN.json", "@kbn/translations-plugin/translations/ja-JP.json", "@kbn/translations-plugin/translations/fr-FR.json" ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 74eea568788b5..dba2d6e29ae7c 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -53,6 +53,7 @@ const configSchema = schema.object({ onboarding: schema.string({ defaultValue: 'apm-*' }), }), forceSyntheticSource: schema.boolean({ defaultValue: false }), + enabled: schema.boolean({ defaultValue: true }), }); // plugin config diff --git a/x-pack/plugins/canvas/server/index.ts b/x-pack/plugins/canvas/server/index.ts index d6d375b7259ac..d25ad10dd8e34 100644 --- a/x-pack/plugins/canvas/server/index.ts +++ b/x-pack/plugins/canvas/server/index.ts @@ -6,7 +6,15 @@ */ import { PluginInitializerContext } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; + import { CanvasPlugin } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => new CanvasPlugin(initializerContext); + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/config.ts b/x-pack/plugins/cross_cluster_replication/server/config.ts index bac5f917f22a6..4cba6d0707abb 100644 --- a/x-pack/plugins/cross_cluster_replication/server/config.ts +++ b/x-pack/plugins/cross_cluster_replication/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index df6427d5c9ab0..871344a95ac71 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -31,6 +31,7 @@ export const mockKibanaValues = { guidedOnboarding: {}, history: mockHistory, isCloud: false, + isSidebarEnabled: true, navigateToUrl: jest.fn(), productAccess: { hasAppSearchAccess: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx index 0915e17ac6ab1..be5d263d531cd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx @@ -33,6 +33,7 @@ describe('renderApp', () => { licensing: licensingMock.createStart(), security: securityMock.createStart(), }, + isSidebarEnabled: true, } as any; const pluginData = { config: {}, diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index fb6c433d15626..b2d8603cd6314 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -37,7 +37,17 @@ import { mountLicensingLogic } from './shared/licensing'; export const renderApp = ( App: React.FC, - { params, core, plugins }: { params: AppMountParameters; core: CoreStart; plugins: PluginsStart }, + { + params, + core, + plugins, + isSidebarEnabled, + }: { + params: AppMountParameters; + core: CoreStart; + plugins: PluginsStart; + isSidebarEnabled: boolean; + }, { config, data }: { config: ClientConfigType; data: ClientData } ) => { const { publicUrl, errorConnectingMessage, ...initialData } = data; @@ -67,6 +77,7 @@ export const renderApp = ( charts: plugins.charts, cloud: plugins.cloud, uiSettings: core.uiSettings, + isSidebarEnabled, guidedOnboarding: plugins.guidedOnboarding, history: params.history, navigateToUrl: core.application.navigateToUrl, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 80178389a8b0d..103c69a0d951e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -32,6 +32,7 @@ type RequiredFieldsOnly = { interface KibanaLogicProps { application: ApplicationStart; config: ClientConfigType; + isSidebarEnabled: boolean; productAccess: ProductAccess; productFeatures: ProductFeatures; // Kibana core @@ -50,6 +51,7 @@ interface KibanaLogicProps { // Optional plugins cloud?: CloudSetup; } + export interface KibanaValues extends Omit { cloud: Partial; isCloud: boolean; @@ -66,6 +68,7 @@ export const KibanaLogic = kea>({ cloud: [props.cloud || {}, {}], guidedOnboarding: [props.guidedOnboarding, {}], history: [props.history, {}], + isSidebarEnabled: [props.isSidebarEnabled, {}], navigateToUrl: [ (url: string, options?: CreateHrefOptions) => { const deps = { history: props.history, http: HttpLogic.values.http }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 4a64094a74fed..31a0185819b0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -30,8 +30,9 @@ import { KibanaLogic } from '../kibana'; import { generateNavLink } from './nav_link_helpers'; export const useEnterpriseSearchNav = () => { - const { productAccess, productFeatures } = useValues(KibanaLogic); + const { isSidebarEnabled, productAccess, productFeatures } = useValues(KibanaLogic); + if (!isSidebarEnabled) return undefined; const enginesSectionEnabled = productAccess.hasSearchEnginesAccess; const navItems: Array> = [ @@ -247,6 +248,7 @@ export const useEnterpriseSearchNav = () => { export const useEnterpriseSearchEngineNav = (engineName?: string, isEmptyState?: boolean) => { const navItems = useEnterpriseSearchNav(); + if (!navItems) return undefined; if (!engineName) return navItems; const searchItem = navItems.find((item) => item.id === 'enginesSearch'); if (!searchItem || !searchItem.items) return navItems; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index 4793d11b56c76..d234b8374d0ae 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -73,7 +73,11 @@ export const EnterpriseSearchPageTemplateWrapper: React.FC = ), }} isEmptyState={isEmptyState && !isLoading} - solutionNav={solutionNav ? { icon: 'logoEnterpriseSearch', ...solutionNav } : undefined} + solutionNav={ + solutionNav && solutionNav.items + ? { icon: 'logoEnterpriseSearch', ...solutionNav } + : undefined + } > {setPageChrome} {readOnlyMode && ( diff --git a/x-pack/plugins/enterprise_search/public/index.ts b/x-pack/plugins/enterprise_search/public/index.ts index b24616fc4b3d8..8dc84c6934e42 100644 --- a/x-pack/plugins/enterprise_search/public/index.ts +++ b/x-pack/plugins/enterprise_search/public/index.ts @@ -12,3 +12,5 @@ import { EnterpriseSearchPlugin } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => { return new EnterpriseSearchPlugin(initializerContext); }; + +export type { EnterpriseSearchPublicSetup, EnterpriseSearchPublicStart } from './plugin'; diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 3390c407993f0..04bc5c3358719 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -41,6 +41,9 @@ export interface ClientData extends InitialAppData { errorConnectingMessage?: string; } +export type EnterpriseSearchPublicSetup = ReturnType; +export type EnterpriseSearchPublicStart = ReturnType; + interface PluginsSetup { cloud?: CloudSetup; home?: HomePublicPluginSetup; @@ -60,6 +63,7 @@ export class EnterpriseSearchPlugin implements Plugin { private config: ClientConfigType; private hasInitialized: boolean = false; private data: ClientData = {} as ClientData; + private isSidebarEnabled: boolean = true; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); @@ -289,6 +293,14 @@ export class EnterpriseSearchPlugin implements Plugin { showOnHomePage: false, }); } + + return { + navigation: { + setIsSidebarEnabled: (enabled: boolean) => { + this.isSidebarEnabled = enabled; + }, + }, + }; } public start(core: CoreStart) { @@ -313,7 +325,7 @@ export class EnterpriseSearchPlugin implements Plugin { : undefined; const plugins = { ...pluginsStart, cloud } as PluginsStart; - return { params, core: coreStart, plugins }; + return { params, core: coreStart, plugins, isSidebarEnabled: this.isSidebarEnabled }; } private getPluginData() { diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts index 40e2c349b9605..c49368411c0e7 100644 --- a/x-pack/plugins/enterprise_search/server/index.ts +++ b/x-pack/plugins/enterprise_search/server/index.ts @@ -19,6 +19,7 @@ export const configSchema = schema.object({ accessCheckTimeoutWarning: schema.number({ defaultValue: 300 }), canDeployEntSearch: schema.boolean({ defaultValue: true }), customHeaders: schema.maybe(schema.object({}, { unknowns: 'allow' })), + enabled: schema.boolean({ defaultValue: true }), hasConnectors: schema.boolean({ defaultValue: true }), hasDefaultIngestPipeline: schema.boolean({ defaultValue: true }), hasNativeConnectors: schema.boolean({ defaultValue: true }), diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index fa30f2b8f7f33..a275a55a48206 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -99,7 +99,7 @@ export function calculatePackagePrivilegesFromCapabilities( return { ...acc, [privilege]: { - executePackageAction: capabilities.siem[privilegeName] || false, + executePackageAction: (capabilities.siem && capabilities.siem[privilegeName]) || false, }, }; }, diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts index d5312bf9bc65c..41eb7e03e29c7 100644 --- a/x-pack/plugins/fleet/server/config.ts +++ b/x-pack/plugins/fleet/server/config.ts @@ -158,6 +158,7 @@ export const config: PluginConfigDescriptor = { } }, }), + enabled: schema.boolean({ defaultValue: true }), }), }; diff --git a/x-pack/plugins/index_lifecycle_management/server/config.ts b/x-pack/plugins/index_lifecycle_management/server/config.ts index 737cc6a472c7a..7fdec20bbb050 100644 --- a/x-pack/plugins/index_lifecycle_management/server/config.ts +++ b/x-pack/plugins/index_lifecycle_management/server/config.ts @@ -24,6 +24,11 @@ const schemaLatest = schema.object( }), // Cloud requires the ability to hide internal node attributes from users. filteredNodeAttributes: schema.arrayOf(schema.string(), { defaultValue: [] }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/license_management/server/config.ts b/x-pack/plugins/license_management/server/config.ts index 42beba0ea5c09..23449bc19e793 100644 --- a/x-pack/plugins/license_management/server/config.ts +++ b/x-pack/plugins/license_management/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/observability/public/components/shared/page_template/lazy_page_template.tsx b/x-pack/plugins/observability/public/components/shared/page_template/lazy_page_template.tsx index 7c61cae4f2c73..7bbc0dbc9ea70 100644 --- a/x-pack/plugins/observability/public/components/shared/page_template/lazy_page_template.tsx +++ b/x-pack/plugins/observability/public/components/shared/page_template/lazy_page_template.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; import type { ObservabilityPageTemplateDependencies, WrappedPageTemplateProps, @@ -15,12 +16,23 @@ export const LazyObservabilityPageTemplate = React.lazy(() => import('./page_tem export type LazyObservabilityPageTemplateProps = WrappedPageTemplateProps; -export function createLazyObservabilityPageTemplate( - injectedDeps: ObservabilityPageTemplateDependencies -) { - return (pageTemplateProps: LazyObservabilityPageTemplateProps) => ( - - - - ); +export function createLazyObservabilityPageTemplate({ + isSidebarEnabled$, + ...injectedDeps +}: ObservabilityPageTemplateDependencies) { + return (pageTemplateProps: LazyObservabilityPageTemplateProps) => { + const isSidebarEnabled = useObservable(isSidebarEnabled$, true); + const { showSolutionNav: showSolutionNavProp, ...props } = pageTemplateProps; + let showSolutionNav = showSolutionNavProp; + + if (!isSidebarEnabled) { + showSolutionNav = false; + } + + return ( + + + + ); + }; } diff --git a/x-pack/plugins/observability/public/components/shared/page_template/page_template.test.tsx b/x-pack/plugins/observability/public/components/shared/page_template/page_template.test.tsx index 2a5324ddb5397..ae221fce17319 100644 --- a/x-pack/plugins/observability/public/components/shared/page_template/page_template.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/page_template/page_template.test.tsx @@ -56,6 +56,7 @@ describe('Page template', () => { navigationSections$: navigationRegistry.sections$, getPageTemplateServices, guidedOnboardingApi: guidedOnboardingMock.createStart().guidedOnboardingApi, + isSidebarEnabled$: of(true), }); const component = shallow( diff --git a/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx b/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx index aa6f99d862a4c..901e80db67a0b 100644 --- a/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx +++ b/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx @@ -53,9 +53,13 @@ export interface ObservabilityPageTemplateDependencies { navigationSections$: Observable; getPageTemplateServices: () => KibanaPageTemplateKibanaDependencies; guidedOnboardingApi: GuidedOnboardingPluginStart['guidedOnboardingApi']; + isSidebarEnabled$: Observable; } -export type ObservabilityPageTemplateProps = ObservabilityPageTemplateDependencies & +export type ObservabilityPageTemplateProps = Omit< + ObservabilityPageTemplateDependencies, + 'isSidebarEnabled$' +> & WrappedPageTemplateProps; export function ObservabilityPageTemplate({ diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 2521041a4ec0a..224b2a41a3ae0 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -313,6 +313,7 @@ export class Plugin observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry, navigation: { registerSections: this.navigationRegistry.registerSections, + setIsSidebarEnabled: this.navigationRegistry.setIsSidebarEnabled, }, useRulesLink: createUseRulesLink(), }; @@ -335,6 +336,7 @@ export class Plugin navigationSections$: this.navigationRegistry.sections$, guidedOnboardingApi: pluginsStart.guidedOnboarding.guidedOnboardingApi, getPageTemplateServices: () => ({ coreStart }), + isSidebarEnabled$: this.navigationRegistry.isSidebarEnabled$, }); const getAsyncO11yAlertsTableConfiguration = async () => { diff --git a/x-pack/plugins/observability/public/services/navigation_registry.ts b/x-pack/plugins/observability/public/services/navigation_registry.ts index 5f10a6f6c6851..e18929799b5fb 100644 --- a/x-pack/plugins/observability/public/services/navigation_registry.ts +++ b/x-pack/plugins/observability/public/services/navigation_registry.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { combineLatest, Observable, ReplaySubject } from 'rxjs'; +import { combineLatest, Observable, ReplaySubject, BehaviorSubject } from 'rxjs'; import { map, scan, shareReplay, switchMap } from 'rxjs/operators'; export interface NavigationSection { @@ -45,6 +45,8 @@ export interface NavigationEntry { export interface NavigationRegistry { registerSections: (sections$: Observable) => void; sections$: Observable; + isSidebarEnabled$: Observable; + setIsSidebarEnabled: (enabled: boolean) => void; } export const createNavigationRegistry = (): NavigationRegistry => { @@ -54,6 +56,8 @@ export const createNavigationRegistry = (): NavigationRegistry => { registeredSections$.next(sections$); }; + const isSidebarEnabled$ = new BehaviorSubject(true); + const sections$: Observable = registeredSections$.pipe( scan( (accumulatedSections$, newSections) => accumulatedSections$.add(newSections), @@ -69,5 +73,9 @@ export const createNavigationRegistry = (): NavigationRegistry => { return { registerSections, sections$, + isSidebarEnabled$, + setIsSidebarEnabled: (enabled) => { + isSidebarEnabled$.next(enabled); + }, }; }; diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index f06b6f049a86b..904908f0c0ba7 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -44,6 +44,7 @@ const configSchema = schema.object({ }), }), }), + enabled: schema.boolean({ defaultValue: true }), }); export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/remote_clusters/server/config.ts b/x-pack/plugins/remote_clusters/server/config.ts index 32db006e8171a..4f6c56191cd89 100644 --- a/x-pack/plugins/remote_clusters/server/config.ts +++ b/x-pack/plugins/remote_clusters/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/rollup/server/config.ts b/x-pack/plugins/rollup/server/config.ts index 235202a23db24..953cd4b283f97 100644 --- a/x-pack/plugins/rollup/server/config.ts +++ b/x-pack/plugins/rollup/server/config.ts @@ -22,6 +22,11 @@ const schemaLatest = schema.object( ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/security/public/config.ts b/x-pack/plugins/security/public/config.ts index 440bd8da27d90..6a5a8dac01500 100644 --- a/x-pack/plugins/security/public/config.ts +++ b/x-pack/plugins/security/public/config.ts @@ -9,4 +9,5 @@ export interface ConfigType { loginAssistanceMessage: string; showInsecureClusterWarning: boolean; sameSiteCookies: 'Strict' | 'Lax' | 'None' | undefined; + showNavLinks: boolean; } diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts index 49fdd6864ec3d..7e0eef83cca1d 100644 --- a/x-pack/plugins/security/public/management/management_service.test.ts +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -25,6 +25,7 @@ import { rolesManagementApp } from './roles'; import { usersManagementApp } from './users'; const mockSection = createManagementSectionMock(); +const mockSetIsSidebarEnabled = (enabled: boolean) => {}; describe('ManagementService', () => { describe('setup()', () => { @@ -41,6 +42,7 @@ describe('ManagementService', () => { } as DefinedSections, }, locator: {} as any, + setIsSidebarEnabled: mockSetIsSidebarEnabled, }; const service = new ManagementService(); @@ -103,6 +105,7 @@ describe('ManagementService', () => { } as DefinedSections, }, locator: {} as any, + setIsSidebarEnabled: mockSetIsSidebarEnabled, }; service.setup({ diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 91d0c33ade107..e1af50e986450 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -29,6 +29,7 @@ interface SetupDeps { securityLicense: SecurityLicense; logoutUrl: string; securityApiClients: SecurityApiClients; + showNavLinks?: boolean; } interface StartDeps { @@ -54,16 +55,18 @@ export class SecurityNavControlService { private securityApiClients!: SecurityApiClients; private navControlRegistered!: boolean; + private showNavLinks!: boolean; private securityFeaturesSubscription?: Subscription; private readonly stop$ = new ReplaySubject(1); private userMenuLinks$ = new BehaviorSubject([]); - public setup({ securityLicense, logoutUrl, securityApiClients }: SetupDeps) { + public setup({ securityLicense, logoutUrl, securityApiClients, showNavLinks = true }: SetupDeps) { this.securityLicense = securityLicense; this.logoutUrl = logoutUrl; this.securityApiClients = securityApiClients; + this.showNavLinks = showNavLinks; } public start({ core, authc }: StartDeps): SecurityNavControlServiceStart { @@ -72,7 +75,7 @@ export class SecurityNavControlService { const isAnonymousPath = core.http.anonymousPaths.isAnonymous(window.location.pathname); const shouldRegisterNavControl = - !isAnonymousPath && showLinks && !this.navControlRegistered; + this.showNavLinks && !isAnonymousPath && showLinks && !this.navControlRegistered; if (shouldRegisterNavControl) { this.registerSecurityNavControl(core, authc); } diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index c56c40f63b4d0..084a34e635dcf 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -107,6 +107,7 @@ export class SecurityPlugin securityLicense: license, logoutUrl: getLogoutUrl(core.http), securityApiClients: this.securityApiClients, + showNavLinks: this.config.showNavLinks, }); this.analyticsService.setup({ diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 8b7324e70d646..ea255d61ee255 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -60,6 +60,7 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "loginAssistanceMessage": "", "public": Object {}, @@ -70,6 +71,7 @@ describe('config schema', () => { "lifespan": "P30D", }, "showInsecureClusterWarning": true, + "showNavLinks": true, } `); @@ -113,6 +115,7 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "loginAssistanceMessage": "", "public": Object {}, @@ -123,6 +126,7 @@ describe('config schema', () => { "lifespan": "P30D", }, "showInsecureClusterWarning": true, + "showNavLinks": true, } `); @@ -166,6 +170,7 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "enabled": true, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -175,6 +180,7 @@ describe('config schema', () => { "lifespan": "P30D", }, "showInsecureClusterWarning": true, + "showNavLinks": true, } `); }); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index e3584427964f3..91abf77a376f8 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -204,6 +204,7 @@ export const ConfigSchema = schema.object({ loginAssistanceMessage: schema.string({ defaultValue: '' }), showInsecureClusterWarning: schema.boolean({ defaultValue: true }), loginHelp: schema.maybe(schema.string()), + showNavLinks: schema.boolean({ defaultValue: true }), cookieName: schema.string({ defaultValue: 'sid' }), encryptionKey: schema.conditional( schema.contextRef('dist'), @@ -295,6 +296,7 @@ export const ConfigSchema = schema.object({ ) ), }), + enabled: schema.boolean({ defaultValue: true }), }); export function createConfig( diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 89ddb41375a91..06ba1e77118e9 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -52,6 +52,7 @@ export const config: PluginConfigDescriptor> = { loginAssistanceMessage: true, showInsecureClusterWarning: true, sameSiteCookies: true, + showNavLinks: true, }, }; export const plugin: PluginInitializer< 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 87d43742a9433..6c6d0da1c031e 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..bd6f36242f806 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 @@ -52,7 +52,7 @@ jest.mock('../../../links', () => ({ })); const mockUseAppNavLinks = jest.fn(); -jest.mock('../nav_links', () => ({ +jest.mock('../../../links/nav_links', () => ({ useAppNavLinks: () => mockUseAppNavLinks(), })); jest.mock('../../links', () => ({ 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/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 29f69700afdb7..291edad3b7fd5 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -101,6 +101,7 @@ export interface SecuritySolutionTabNavigationProps { } export type NavigateToUrl = (url: string) => void; + export interface NavLinkItem { categories?: LinkCategories; description?: string; 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 efa9ce4831be7..01b67365b6926 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' }, @@ -176,6 +177,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/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/dashboards.test.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.test.tsx index 97eb89695bc98..112786b861df3 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.test.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.test.tsx @@ -10,9 +10,9 @@ import React from 'react'; import { SecurityPageName } from '../../app/types'; import { TestProviders } from '../../common/mock'; import { DashboardsLandingPage } from './dashboards'; -import type { NavLinkItem } from '../../common/components/navigation/types'; import { useCapabilities } from '../../common/lib/kibana'; import * as telemetry from '../../common/lib/telemetry'; +import type { NavigationLink } from '../../common/links'; jest.mock('../../common/lib/kibana'); jest.mock('../../common/utils/route/spy_routes', () => ({ SpyRoute: () => null })); @@ -28,7 +28,7 @@ const spyTrack = jest.spyOn(telemetry, 'track'); const OVERVIEW_ITEM_LABEL = 'Overview'; const DETECTION_RESPONSE_ITEM_LABEL = 'Detection & Response'; -const APP_DASHBOARD_LINKS: NavLinkItem = { +const APP_DASHBOARD_LINKS: NavigationLink = { id: SecurityPageName.dashboardsLanding, title: 'Dashboards', links: [ @@ -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/landing_pages/pages/dashboards.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx index ee97955b659ab..1bd7b69adcb72 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx @@ -18,7 +18,7 @@ import { LEGACY_DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public'; import { SecurityPageName } from '../../app/types'; import { DashboardsTable } from '../../common/components/dashboards/dashboards_table'; import { Title } from '../../common/components/header_page/title'; -import { useAppRootNavLink } from '../../common/components/navigation/nav_links'; +import { useRootNavLink } from '../../common/links/nav_links'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useCreateSecurityDashboardLink } from '../../common/containers/dashboards/use_create_security_dashboard_link'; import { useCapabilities, useNavigateTo } from '../../common/lib/kibana'; @@ -60,7 +60,7 @@ const Header: React.FC<{ canCreateDashboard: boolean }> = ({ canCreateDashboard }; export const DashboardsLandingPage = () => { - const dashboardLinks = useAppRootNavLink(SecurityPageName.dashboardsLanding)?.links ?? []; + const dashboardLinks = useRootNavLink(SecurityPageName.dashboardsLanding)?.links ?? []; const { show: canReadDashboard, createNew: canCreateDashboard } = useCapabilities(LEGACY_DASHBOARD_APP_ID); 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/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 634e488bf14ce..d7655744869ca 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(); @@ -167,6 +169,7 @@ export class Plugin implements IPlugin SecuritySolutionTemplateWrapper, }, + isSidebarEnabled$: this.isSidebarEnabled$, telemetry: this.telemetry.start(), }; return services; @@ -241,10 +244,13 @@ export class Plugin implements IPlugin { + this.isSidebarEnabled$.next(enabled); + }, }; } - public start(core: CoreStart, plugins: StartPlugins) { + public start(core: CoreStart, plugins: StartPlugins): PluginStart { KibanaServices.init({ ...core, ...plugins, @@ -305,7 +311,9 @@ export class Plugin implements IPlugin typeof SecuritySolutionTemplateWrapper; }; + isSidebarEnabled$: BehaviorSubject; telemetry: TelemetryClientStart; }; export interface PluginSetup { resolver: () => Promise; + setIsSidebarEnabled: (enabled: boolean) => void; +} + +export interface PluginStart { + navLinks$: Observable; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface PluginStart {} export interface AppObservableLibs { kibana: CoreStart; diff --git a/x-pack/plugins/security_solution/server/config.mock.ts b/x-pack/plugins/security_solution/server/config.mock.ts index c1faa6f401a1d..6fcaa94629643 100644 --- a/x-pack/plugins/security_solution/server/config.mock.ts +++ b/x-pack/plugins/security_solution/server/config.mock.ts @@ -31,6 +31,7 @@ export const createMockConfig = (): ConfigType => { alertIgnoreFields: [], experimentalFeatures: parseExperimentalConfigValue(enableExperimental), + enabled: true, }; }; diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index 26f1be4f014b3..a0283858590cb 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -122,6 +122,7 @@ export const configSchema = schema.object({ * the package is not already installed. */ prebuiltRulesPackageVersion: schema.maybe(schema.string()), + enabled: schema.boolean({ defaultValue: true }), }); export type ConfigSchema = TypeOf; diff --git a/x-pack/plugins/serverless/README.md b/x-pack/plugins/serverless/README.md new file mode 100755 index 0000000000000..4d279ec348054 --- /dev/null +++ b/x-pack/plugins/serverless/README.md @@ -0,0 +1,19 @@ +# serverless + +A Kibana plugin + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. + +## Scripts + +
+
yarn kbn bootstrap
+
Execute this to install node_modules and setup the dependencies in your plugin and in Kibana
+ +
yarn plugin-helpers build
+
Execute this to create a distributable version of this plugin that can be installed in Kibana
+
diff --git a/x-pack/plugins/serverless/common/index.ts b/x-pack/plugins/serverless/common/index.ts new file mode 100644 index 0000000000000..d171cea3f5d68 --- /dev/null +++ b/x-pack/plugins/serverless/common/index.ts @@ -0,0 +1,11 @@ +/* + * 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 = 'serverless'; +export const PLUGIN_NAME = 'serverless'; + +export const API_SWITCH_PROJECT = '/api/serverless/switch_project'; diff --git a/x-pack/plugins/serverless/kibana.jsonc b/x-pack/plugins/serverless/kibana.jsonc new file mode 100644 index 0000000000000..af79877f1079f --- /dev/null +++ b/x-pack/plugins/serverless/kibana.jsonc @@ -0,0 +1,21 @@ +{ + "type": "plugin", + "id": "@kbn/serverless", + "owner": "@elastic/appex-sharedux", + "description": "The core Serverless plugin, providing APIs to Serverless Project plugins.", + "plugin": { + "id": "serverless", + "server": true, + "browser": true, + "configPath": [ + "xpack", + "serverless", + "plugin", + ], + "requiredPlugins": [ + "kibanaReact", + ], + "optionalPlugins": [], + "requiredBundles": [] + } +} \ No newline at end of file diff --git a/x-pack/plugins/serverless/package.json b/x-pack/plugins/serverless/package.json new file mode 100644 index 0000000000000..ec457f89fbcaa --- /dev/null +++ b/x-pack/plugins/serverless/package.json @@ -0,0 +1,11 @@ +{ + "name": "@kbn/serverless", + "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" + } +} \ No newline at end of file diff --git a/x-pack/plugins/serverless/public/components/index.ts b/x-pack/plugins/serverless/public/components/index.ts new file mode 100644 index 0000000000000..c5dd3bd763481 --- /dev/null +++ b/x-pack/plugins/serverless/public/components/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { Switcher } from './switcher'; diff --git a/x-pack/plugins/serverless/public/components/switcher/index.tsx b/x-pack/plugins/serverless/public/components/switcher/index.tsx new file mode 100644 index 0000000000000..6d9b673a676c2 --- /dev/null +++ b/x-pack/plugins/serverless/public/components/switcher/index.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState } from 'react'; +import ReactDOM from 'react-dom'; +import { + EuiPopover, + useGeneratedHtmlId, + EuiPopoverTitle, + EuiTitle, + EuiSpacer, + EuiHeaderSectionItemButton, +} from '@elastic/eui'; +import { HttpStart } from '@kbn/core-http-browser'; +import { API_SWITCH_PROJECT } from '../../../common'; +import { ProjectType } from '../../types'; +import { Loader } from './loader'; +import { SwitcherItem } from './item'; + +export const Switcher = ({ http }: { http: HttpStart }) => { + const [isOpen, setIsOpen] = useState(false); + const id = useGeneratedHtmlId({ + prefix: 'switcherPopover', + }); + + const onButtonClick = () => { + setIsOpen(!isOpen); + }; + + const closePopover = () => { + setIsOpen(false); + }; + + const handleSwitch = (project: ProjectType, e: React.MouseEvent) => { + e.preventDefault(); + closePopover(); + http.post(API_SWITCH_PROJECT, { body: JSON.stringify({ id: project }) }); + ReactDOM.render(, document.body); + setTimeout(() => { + window.location.href = '/'; + }, 2000); + return false; + }; + + const items = ( + <> + handleSwitch('oblt', e)} + /> + + handleSwitch('es', e)} + /> + + handleSwitch('security', e)} + /> + + ); + + const button = ( + + ); + + return ( + + Developer Tools + + +

Switch Project Type

+
+ +
{items}
+
+ ); +}; diff --git a/x-pack/plugins/serverless/public/components/switcher/item.tsx b/x-pack/plugins/serverless/public/components/switcher/item.tsx new file mode 100644 index 0000000000000..e158208acd889 --- /dev/null +++ b/x-pack/plugins/serverless/public/components/switcher/item.tsx @@ -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 React from 'react'; +import { EuiButtonEmpty, type EuiIconProps } from '@elastic/eui'; + +interface ItemProps extends Pick { + label: string; + onClick: React.MouseEventHandler; +} + +export const SwitcherItem = ({ type, label, onClick }: ItemProps) => ( + + {label} + +); diff --git a/x-pack/plugins/serverless/public/components/switcher/loader.tsx b/x-pack/plugins/serverless/public/components/switcher/loader.tsx new file mode 100644 index 0000000000000..d214b7bc271ea --- /dev/null +++ b/x-pack/plugins/serverless/public/components/switcher/loader.tsx @@ -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 React from 'react'; + +import { Logo, type Props } from './logo'; + +export const Loader = (props: Props) => ( +
+
+ +
Loading Project
+
+
+
+); diff --git a/x-pack/plugins/serverless/public/components/switcher/logo.tsx b/x-pack/plugins/serverless/public/components/switcher/logo.tsx new file mode 100644 index 0000000000000..a887a75edcd9b --- /dev/null +++ b/x-pack/plugins/serverless/public/components/switcher/logo.tsx @@ -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 React from 'react'; +import { EuiIcon } from '@elastic/eui'; +import type { ProjectType } from '../../types'; + +export interface Props { + project: ProjectType; +} + +export const Logo = ({ project }: Props) => { + let type = 'logoElastic'; + switch (project) { + case 'es': + type = 'logoElasticsearch'; + break; + case 'security': + type = 'logoSecurity'; + break; + case 'oblt': + type = 'logoObservability'; + break; + } + + return ; +}; diff --git a/x-pack/plugins/serverless/public/index.ts b/x-pack/plugins/serverless/public/index.ts new file mode 100644 index 0000000000000..d47e125a14ab6 --- /dev/null +++ b/x-pack/plugins/serverless/public/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { ServerlessPlugin } from './plugin'; + +export function plugin() { + return new ServerlessPlugin(); +} + +export type { ServerlessPluginSetup, ServerlessPluginStart } from './types'; diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx new file mode 100644 index 0000000000000..48c8e1d2b684c --- /dev/null +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -0,0 +1,54 @@ +/* + * 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 * as Rx from 'rxjs'; +import ReactDOM from 'react-dom'; +import React from 'react'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { CoreSetup, CoreStart, Plugin, CoreTheme } from '@kbn/core/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { HttpStart } from '@kbn/core-http-browser'; + +import { ServerlessPluginSetup, ServerlessPluginStart } from './types'; +import { Switcher } from './components'; + +export class ServerlessPlugin implements Plugin { + public setup(_core: CoreSetup): ServerlessPluginSetup { + return {}; + } + + public start(core: CoreStart): ServerlessPluginStart { + core.chrome.setChromeStyle('solution'); + + if (process.env.NODE_ENV !== 'production') { + core.chrome.navControls.registerRight({ + order: 500, + mount: (target) => this.mount(target, core.theme.theme$, core.http), + }); + } + + return { + setServerlessNavigation: (navigation: JSX.Element) => + core.chrome.setSolutionNavigation(navigation), + }; + } + + public stop() {} + + private mount(targetDomElement: HTMLElement, theme$: Rx.Observable, http: HttpStart) { + ReactDOM.render( + + + + + , + targetDomElement + ); + + return () => ReactDOM.unmountComponentAtNode(targetDomElement); + } +} diff --git a/x-pack/plugins/serverless/public/types.ts b/x-pack/plugins/serverless/public/types.ts new file mode 100644 index 0000000000000..787ea16daec9f --- /dev/null +++ b/x-pack/plugins/serverless/public/types.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessPluginSetup {} + +export interface ServerlessPluginStart { + setServerlessNavigation: (navigation: JSX.Element) => void; +} + +export type ProjectType = 'oblt' | 'security' | 'es'; diff --git a/x-pack/plugins/serverless/server/config.ts b/x-pack/plugins/serverless/server/config.ts new file mode 100644 index 0000000000000..dce4deda86049 --- /dev/null +++ b/x-pack/plugins/serverless/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 ServerlessConfig = TypeOf; diff --git a/x-pack/plugins/serverless/server/index.ts b/x-pack/plugins/serverless/server/index.ts new file mode 100644 index 0000000000000..ae805970e038e --- /dev/null +++ b/x-pack/plugins/serverless/server/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { ServerlessPlugin } 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 ServerlessPlugin(initializerContext); +} + +export type { ServerlessPluginSetup, ServerlessPluginStart } from './types'; diff --git a/x-pack/plugins/serverless/server/plugin.ts b/x-pack/plugins/serverless/server/plugin.ts new file mode 100644 index 0000000000000..1cc3d4973e50e --- /dev/null +++ b/x-pack/plugins/serverless/server/plugin.ts @@ -0,0 +1,63 @@ +/* + * 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 { copyFileSync, existsSync } from 'fs'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/server'; + +import { schema, TypeOf } from '@kbn/config-schema'; +import { getConfigDirectory } from '@kbn/utils'; +import { resolve } from 'path'; +import { ServerlessPluginSetup, ServerlessPluginStart } from './types'; +import { API_SWITCH_PROJECT } from '../common'; + +const switchBodySchema = schema.object({ + id: schema.string(), +}); + +type SwitchReqBody = TypeOf; + +export class ServerlessPlugin implements Plugin { + constructor(_initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + + if (process.env.NODE_ENV !== 'production') { + router.post( + { + path: API_SWITCH_PROJECT, + validate: { + body: switchBodySchema, + }, + }, + async (_context, request, response) => { + const { id } = request.body; + const path = resolve(getConfigDirectory(), `serverless.${id}.yml`); + + try { + if (existsSync(path)) { + copyFileSync(path, resolve(getConfigDirectory(), 'serverless.recent.yml')); + return response.ok({ body: id }); + } + } catch (e) { + return response.badRequest({ body: e }); + } + + return response.badRequest(); + } + ); + } + + return {}; + } + + public start(_core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless/server/types.ts b/x-pack/plugins/serverless/server/types.ts new file mode 100644 index 0000000000000..92a804b34a948 --- /dev/null +++ b/x-pack/plugins/serverless/server/types.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessPluginSetup {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessPluginStart {} diff --git a/x-pack/plugins/serverless/tsconfig.json b/x-pack/plugins/serverless/tsconfig.json new file mode 100644 index 0000000000000..444ba6119409f --- /dev/null +++ b/x-pack/plugins/serverless/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", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/config-schema", + "@kbn/core-http-browser", + "@kbn/kibana-react-plugin", + "@kbn/i18n-react", + "@kbn/utils", + ] +} diff --git a/x-pack/plugins/serverless_observability/.gitignore b/x-pack/plugins/serverless_observability/.gitignore new file mode 100644 index 0000000000000..c3dca1b96fcc2 --- /dev/null +++ b/x-pack/plugins/serverless_observability/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/x-pack/plugins/serverless_observability/README.md b/x-pack/plugins/serverless_observability/README.md new file mode 100755 index 0000000000000..25d55bd95bb40 --- /dev/null +++ b/x-pack/plugins/serverless_observability/README.md @@ -0,0 +1,3 @@ +# serverlessObservability + +A witty, fitting description to come. \ No newline at end of file diff --git a/x-pack/plugins/serverless_observability/common/index.ts b/x-pack/plugins/serverless_observability/common/index.ts new file mode 100644 index 0000000000000..d6a5ea767034c --- /dev/null +++ b/x-pack/plugins/serverless_observability/common/index.ts @@ -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 = 'serverlessObservability'; +export const PLUGIN_NAME = 'serverlessObservability'; diff --git a/x-pack/plugins/serverless_observability/kibana.jsonc b/x-pack/plugins/serverless_observability/kibana.jsonc new file mode 100644 index 0000000000000..abce6cd9e8350 --- /dev/null +++ b/x-pack/plugins/serverless_observability/kibana.jsonc @@ -0,0 +1,22 @@ +{ + "type": "plugin", + "id": "@kbn/serverless-observability", + "owner": "@elastic/appex-sharedux", + "description": "Serverless customizations for observability.", + "plugin": { + "id": "serverlessObservability", + "server": true, + "browser": true, + "configPath": [ + "xpack", + "serverless", + "observability" + ], + "requiredPlugins": [ + "serverless", + "observability" + ], + "optionalPlugins": [], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/serverless_observability/package.json b/x-pack/plugins/serverless_observability/package.json new file mode 100644 index 0000000000000..64b310d7eabae --- /dev/null +++ b/x-pack/plugins/serverless_observability/package.json @@ -0,0 +1,11 @@ +{ + "name": "@kbn/serverless-observability", + "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" + } +} \ No newline at end of file diff --git a/x-pack/plugins/serverless_observability/public/index.ts b/x-pack/plugins/serverless_observability/public/index.ts new file mode 100644 index 0000000000000..a785b68735375 --- /dev/null +++ b/x-pack/plugins/serverless_observability/public/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { ServerlessObservabilityPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new ServerlessObservabilityPlugin(); +} + +export type { + ServerlessObservabilityPluginSetup, + ServerlessObservabilityPluginStart, +} from './types'; diff --git a/x-pack/plugins/serverless_observability/public/plugin.tsx b/x-pack/plugins/serverless_observability/public/plugin.tsx new file mode 100644 index 0000000000000..e6438ee8feff6 --- /dev/null +++ b/x-pack/plugins/serverless_observability/public/plugin.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { + ServerlessObservabilityPluginSetup, + ServerlessObservabilityPluginStart, + ServerlessObservabilityPluginSetupDependencies, + ServerlessObservabilityPluginStartDependencies, +} from './types'; + +export class ServerlessObservabilityPlugin + implements Plugin +{ + public setup( + _core: CoreSetup, + setupDeps: ServerlessObservabilityPluginSetupDependencies + ): ServerlessObservabilityPluginSetup { + setupDeps.observability.navigation.setIsSidebarEnabled(false); + + // Return methods that should be available to other plugins + return {}; + } + + public start( + _core: CoreStart, + { serverless }: ServerlessObservabilityPluginStartDependencies + ): ServerlessObservabilityPluginStart { + serverless.setServerlessNavigation(

Observability

); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_observability/public/types.ts b/x-pack/plugins/serverless_observability/public/types.ts new file mode 100644 index 0000000000000..e0fca61302ad7 --- /dev/null +++ b/x-pack/plugins/serverless_observability/public/types.ts @@ -0,0 +1,28 @@ +/* + * 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 { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import { + ObservabilityPublicSetup, + ObservabilityPublicStart, +} from '@kbn/observability-plugin/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessObservabilityPluginSetup {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessObservabilityPluginStart {} + +export interface ServerlessObservabilityPluginSetupDependencies { + observability: ObservabilityPublicSetup; + serverless: ServerlessPluginSetup; +} + +export interface ServerlessObservabilityPluginStartDependencies { + observability: ObservabilityPublicStart; + serverless: ServerlessPluginStart; +} diff --git a/x-pack/plugins/serverless_observability/server/config.ts b/x-pack/plugins/serverless_observability/server/config.ts new file mode 100644 index 0000000000000..599a9f2bd7769 --- /dev/null +++ b/x-pack/plugins/serverless_observability/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 ServerlessObservabilityConfig = TypeOf; diff --git a/x-pack/plugins/serverless_observability/server/index.ts b/x-pack/plugins/serverless_observability/server/index.ts new file mode 100644 index 0000000000000..c45e363a429bf --- /dev/null +++ b/x-pack/plugins/serverless_observability/server/index.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 { PluginInitializerContext } from '@kbn/core/server'; + +import { ServerlessObservabilityPlugin } 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 ServerlessObservabilityPlugin(initializerContext); +} + +export type { + ServerlessObservabilityPluginSetup, + ServerlessObservabilityPluginStart, +} from './types'; diff --git a/x-pack/plugins/serverless_observability/server/plugin.ts b/x-pack/plugins/serverless_observability/server/plugin.ts new file mode 100644 index 0000000000000..8b28ba2b0a4ac --- /dev/null +++ b/x-pack/plugins/serverless_observability/server/plugin.ts @@ -0,0 +1,26 @@ +/* + * 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 { ServerlessObservabilityPluginSetup, ServerlessObservabilityPluginStart } from './types'; + +export class ServerlessObservabilityPlugin + implements Plugin +{ + constructor(_initializerContext: PluginInitializerContext) {} + + public setup() { + return {}; + } + + public start() { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_observability/server/types.ts b/x-pack/plugins/serverless_observability/server/types.ts new file mode 100644 index 0000000000000..f8a587103e886 --- /dev/null +++ b/x-pack/plugins/serverless_observability/server/types.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessObservabilityPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessObservabilityPluginStart {} diff --git a/x-pack/plugins/serverless_observability/tsconfig.json b/x-pack/plugins/serverless_observability/tsconfig.json new file mode 100644 index 0000000000000..8c9ec19e4736a --- /dev/null +++ b/x-pack/plugins/serverless_observability/tsconfig.json @@ -0,0 +1,23 @@ +{ + "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/observability-plugin", + "@kbn/serverless", + ] +} diff --git a/x-pack/plugins/serverless_search/.gitignore b/x-pack/plugins/serverless_search/.gitignore new file mode 100644 index 0000000000000..c3dca1b96fcc2 --- /dev/null +++ b/x-pack/plugins/serverless_search/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/x-pack/plugins/serverless_search/README.md b/x-pack/plugins/serverless_search/README.md new file mode 100755 index 0000000000000..9758a7616785d --- /dev/null +++ b/x-pack/plugins/serverless_search/README.md @@ -0,0 +1,3 @@ +# serverlessSearch + +A witty, fitting description to come. diff --git a/x-pack/plugins/serverless_search/common/index.ts b/x-pack/plugins/serverless_search/common/index.ts new file mode 100644 index 0000000000000..539748f2cea38 --- /dev/null +++ b/x-pack/plugins/serverless_search/common/index.ts @@ -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 = 'serverlessSearch'; +export const PLUGIN_NAME = 'serverlessSearch'; diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc new file mode 100644 index 0000000000000..b548823567ce0 --- /dev/null +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -0,0 +1,23 @@ +{ + "type": "plugin", + "id": "@kbn/serverless-search", + "owner": "@elastic/appex-sharedux", + "description": "Serverless customizations for search.", + "plugin": { + "id": "serverlessSearch", + "server": true, + "browser": true, + "configPath": [ + "xpack", + "serverless", + "search" + ], + "requiredPlugins": [ + "serverless", + "enterpriseSearch", + "management" + ], + "optionalPlugins": [], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/serverless_search/package.json b/x-pack/plugins/serverless_search/package.json new file mode 100644 index 0000000000000..b7820231076ee --- /dev/null +++ b/x-pack/plugins/serverless_search/package.json @@ -0,0 +1,11 @@ +{ + "name": "@kbn/serverless-search", + "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" + } +} diff --git a/x-pack/plugins/serverless_search/public/index.ts b/x-pack/plugins/serverless_search/public/index.ts new file mode 100644 index 0000000000000..5031ccc61d1ac --- /dev/null +++ b/x-pack/plugins/serverless_search/public/index.ts @@ -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 { ServerlessSearchPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new ServerlessSearchPlugin(); +} + +export type { ServerlessSearchPluginSetup, ServerlessSearchPluginStart } from './types'; diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx new file mode 100644 index 0000000000000..b4ebbf328da1a --- /dev/null +++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiCollapsibleNavGroup, EuiListGroup } from '@elastic/eui'; +import { ApplicationStart, HttpSetup } from '@kbn/core/public'; + +export interface ServerlessSearchCollapsibleNavigationProps { + http: HttpSetup; + navigateToUrl: ApplicationStart['navigateToUrl']; +} + +export const ServerlessSearchCollapsibleNavigation = ({ + http, + navigateToUrl, +}: ServerlessSearchCollapsibleNavigationProps) => { + const navigateTo = (url: string) => () => { + navigateToUrl(http.basePath.prepend(url)); + }; + const navItems = [ + { + label: 'Overview', + onClick: navigateTo('/app/enterprise_search/overview'), + }, + { + label: 'Indices', + onClick: navigateTo('/app/enterprise_search/content/search_indices'), + }, + { + label: 'Engines', + onClick: navigateTo('/app/enterprise_search/content/engines'), + }, + { + label: 'API keys', + onClick: navigateTo('/app/management/security/api_keys'), + }, + { + label: 'Ingest pipelines', + onClick: navigateTo('/app/management/ingest/ingest_pipelines'), + }, + ]; + + return ( + <> + + + + + ); +}; diff --git a/x-pack/plugins/serverless_search/public/plugin.tsx b/x-pack/plugins/serverless_search/public/plugin.tsx new file mode 100644 index 0000000000000..1b2a29452ca9d --- /dev/null +++ b/x-pack/plugins/serverless_search/public/plugin.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { + ServerlessSearchPluginSetup, + ServerlessSearchPluginSetupDependencies, + ServerlessSearchPluginStart, + ServerlessSearchPluginStartDependencies, +} from './types'; + +import { ServerlessSearchCollapsibleNavigation } from './layout/nav'; + +export class ServerlessSearchPlugin + implements Plugin +{ + public setup( + _core: CoreSetup, + setupDeps: ServerlessSearchPluginSetupDependencies + ): ServerlessSearchPluginSetup { + setupDeps.enterpriseSearch.navigation.setIsSidebarEnabled(false); + setupDeps.management.setIsSidebarEnabled(false); + return {}; + } + + public start( + core: CoreStart, + { serverless }: ServerlessSearchPluginStartDependencies + ): ServerlessSearchPluginStart { + serverless.setServerlessNavigation( + + ); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts new file mode 100644 index 0000000000000..ad66e6df85c74 --- /dev/null +++ b/x-pack/plugins/serverless_search/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 { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; +import { + EnterpriseSearchPublicSetup, + EnterpriseSearchPublicStart, +} from '@kbn/enterprise-search-plugin/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSearchPluginSetup {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSearchPluginStart {} + +export interface ServerlessSearchPluginSetupDependencies { + enterpriseSearch: EnterpriseSearchPublicSetup; + management: ManagementSetup; + serverless: ServerlessPluginSetup; +} + +export interface ServerlessSearchPluginStartDependencies { + enterpriseSearch: EnterpriseSearchPublicStart; + management: ManagementStart; + serverless: ServerlessPluginStart; +} diff --git a/x-pack/plugins/serverless_search/server/config.ts b/x-pack/plugins/serverless_search/server/config.ts new file mode 100644 index 0000000000000..546c594aaabfb --- /dev/null +++ b/x-pack/plugins/serverless_search/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 ServerlessSearchConfig = TypeOf; diff --git a/x-pack/plugins/serverless_search/server/index.ts b/x-pack/plugins/serverless_search/server/index.ts new file mode 100644 index 0000000000000..90e0b170d4a71 --- /dev/null +++ b/x-pack/plugins/serverless_search/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 { ServerlessSearchPlugin } 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 ServerlessSearchPlugin(initializerContext); +} + +export type { ServerlessSearchPluginSetup, ServerlessSearchPluginStart } from './types'; diff --git a/x-pack/plugins/serverless_search/server/plugin.ts b/x-pack/plugins/serverless_search/server/plugin.ts new file mode 100644 index 0000000000000..99d9bf01da0df --- /dev/null +++ b/x-pack/plugins/serverless_search/server/plugin.ts @@ -0,0 +1,26 @@ +/* + * 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 { ServerlessSearchPluginSetup, ServerlessSearchPluginStart } from './types'; + +export class ServerlessSearchPlugin + implements Plugin +{ + constructor(_initializerContext: PluginInitializerContext) {} + + public setup() { + return {}; + } + + public start() { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_search/server/types.ts b/x-pack/plugins/serverless_search/server/types.ts new file mode 100644 index 0000000000000..6011e2eb60fa0 --- /dev/null +++ b/x-pack/plugins/serverless_search/server/types.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSearchPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSearchPluginStart {} diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json new file mode 100644 index 0000000000000..c8150e5a71926 --- /dev/null +++ b/x-pack/plugins/serverless_search/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/enterprise-search-plugin", + "@kbn/management-plugin", + "@kbn/serverless", + ] +} diff --git a/x-pack/plugins/serverless_security/.gitignore b/x-pack/plugins/serverless_security/.gitignore new file mode 100644 index 0000000000000..c3dca1b96fcc2 --- /dev/null +++ b/x-pack/plugins/serverless_security/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/x-pack/plugins/serverless_security/README.md b/x-pack/plugins/serverless_security/README.md new file mode 100755 index 0000000000000..33d229a31060d --- /dev/null +++ b/x-pack/plugins/serverless_security/README.md @@ -0,0 +1,3 @@ +# serverlessSecurity + +A witty, fitting description to come. \ No newline at end of file diff --git a/x-pack/plugins/serverless_security/common/index.ts b/x-pack/plugins/serverless_security/common/index.ts new file mode 100644 index 0000000000000..0dc5be6ddf9bb --- /dev/null +++ b/x-pack/plugins/serverless_security/common/index.ts @@ -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'; diff --git a/x-pack/plugins/serverless_security/kibana.jsonc b/x-pack/plugins/serverless_security/kibana.jsonc new file mode 100644 index 0000000000000..99def198e6b46 --- /dev/null +++ b/x-pack/plugins/serverless_security/kibana.jsonc @@ -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": [] + } +} \ No newline at end of file diff --git a/x-pack/plugins/serverless_security/package.json b/x-pack/plugins/serverless_security/package.json new file mode 100644 index 0000000000000..0154207c22d6f --- /dev/null +++ b/x-pack/plugins/serverless_security/package.json @@ -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" + } +} \ No newline at end of file diff --git a/x-pack/plugins/serverless_security/public/components/side_navigation/index.ts b/x-pack/plugins/serverless_security/public/components/side_navigation/index.ts new file mode 100644 index 0000000000000..6ac2942940587 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/components/side_navigation/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { SecuritySideNavigation } from './side_navigation'; diff --git a/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.tsx b/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.tsx new file mode 100644 index 0000000000000..1f7ca7821d291 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/components/side_navigation/side_navigation.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLoadingSpinner, useEuiTheme } from '@elastic/eui'; +import { SolutionNav } from '@kbn/shared-ux-page-solution-nav'; +import { SolutionSideNav } from '@kbn/security-solution-side-nav'; +import { + usePartitionFooterNavItems, + useSideNavItems, + useSideNavSelectedId, +} from '../../hooks/use_side_nav_items'; + +export const SecuritySideNavigation: React.FC = () => { + const { euiTheme } = useEuiTheme(); + const sideNavItems = useSideNavItems(); + const selectedId = useSideNavSelectedId(sideNavItems); + const [items, footerItems] = usePartitionFooterNavItems(sideNavItems); + + const isLoading = items.length === 0 && footerItems.length === 0; + + return isLoading ? ( + + ) : ( + + } + closeFlyoutButtonPosition={'inside'} + /> + ); +}; diff --git a/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts b/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts new file mode 100644 index 0000000000000..6cdaef0d94d31 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/hooks/use_link_props.ts @@ -0,0 +1,65 @@ +/* + * 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 { APP_UI_ID, type SecurityPageName } from '@kbn/security-solution-plugin/common'; +import { useMemo, useCallback, type MouseEventHandler, type MouseEvent } from 'react'; +import { useKibana, type Services } from '../services'; + +interface LinkProps { + onClick: MouseEventHandler; + href: string; +} + +interface GetLinkPropsParams { + deepLinkId?: SecurityPageName; + path?: string; + appId?: string; + onClick?: MouseEventHandler; +} + +type GetLinkProps = (params: GetLinkPropsParams) => LinkProps; + +export const useLinkProps: GetLinkProps = (props) => { + const { application } = useKibana().services; + return useMemo(() => getLinkProps({ ...props, application }), [application, props]); +}; + +export const useGetLinkProps: () => GetLinkProps = () => { + const { application } = useKibana().services; + return useCallback( + (props) => getLinkProps({ ...props, application }), + [application] + ); +}; + +const getLinkProps = ({ + deepLinkId, + path, + onClick: onClickProps, + appId = APP_UI_ID, + application, +}: GetLinkPropsParams & { application: Services['application'] }): LinkProps => { + const { getUrlForApp, navigateToUrl } = application; + const url = getUrlForApp(appId, { deepLinkId, path }); + return { + href: url, + onClick: (ev) => { + if (isModifiedEvent(ev)) { + return; + } + + ev.preventDefault(); + navigateToUrl(url); + if (onClickProps) { + onClickProps(ev); + } + }, + }; +}; + +const isModifiedEvent = (event: MouseEvent) => + event.metaKey || event.altKey || event.ctrlKey || event.shiftKey; diff --git a/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts b/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts new file mode 100644 index 0000000000000..dd1cf2551c0ab --- /dev/null +++ b/x-pack/plugins/serverless_security/public/hooks/use_side_nav_items.ts @@ -0,0 +1,144 @@ +/* + * 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 { useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { matchPath, useLocation } from 'react-router-dom'; +import { partition } from 'lodash/fp'; +import { SecurityPageName } from '@kbn/security-solution-plugin/common'; +import type { SolutionSideNavItem } from '@kbn/security-solution-side-nav'; +import { useKibana } from '../services'; +import { useGetLinkProps } from './use_link_props'; + +const isFooterNavItem = (id: string) => + id === SecurityPageName.landing || id === SecurityPageName.administration; + +const isGetStartedNavItem = (id: string) => id === SecurityPageName.landing; + +// DFS for the sideNavItem matching the current `pathname`, returns all item hierarchy when found +const findItemsByPath = ( + sideNavItems: SolutionSideNavItem[], + pathname: string +): SolutionSideNavItem[] => { + for (const sideNavItem of sideNavItems) { + if (sideNavItem.items?.length) { + const found = findItemsByPath(sideNavItem.items, pathname); + if (found.length) { + found.unshift(sideNavItem); + return found; + } + } + if (matchPath(pathname, { path: sideNavItem.href })) { + return [sideNavItem]; + } + } + return []; +}; + +/** + * Returns all the formatted SideNavItems, including external links + */ +export const useSideNavItems = (): SolutionSideNavItem[] => { + const { securitySolution } = useKibana().services; + const navLinks = useObservable(securitySolution.navLinks$, []); + const getLinkProps = useGetLinkProps(); + + const securitySideNavItems = useMemo( + () => + navLinks.reduce((items, navLink) => { + if (navLink.disabled) { + return items; + } + if (isGetStartedNavItem(navLink.id)) { + items.push({ + id: navLink.id, + label: navLink.title.toUpperCase(), + ...getLinkProps({ deepLinkId: navLink.id }), + labelSize: 'xs', + iconType: 'launch', + appendSeparator: true, + }); + } else { + // default sideNavItem formatting + items.push({ + id: navLink.id, + label: navLink.title, + ...getLinkProps({ deepLinkId: navLink.id }), + ...(navLink.categories?.length && { categories: navLink.categories }), + ...(navLink.links?.length && { + items: navLink.links.reduce((acc, current) => { + if (!current.disabled) { + acc.push({ + id: current.id, + label: current.title, + description: current.description, + isBeta: current.isBeta, + betaOptions: current.betaOptions, + ...getLinkProps({ deepLinkId: current.id }), + }); + } + return acc; + }, []), + }), + }); + } + return items; + }, []), + [getLinkProps, navLinks] + ); + + const sideNavItems = useAddExternalSideNavItems(securitySideNavItems); + + return sideNavItems; +}; + +/** + * @param securitySideNavItems the sideNavItems for Security pages + * @returns sideNavItems with Security and external links + */ +const useAddExternalSideNavItems = (securitySideNavItems: SolutionSideNavItem[]) => { + const getLinkProps = useGetLinkProps(); + + const sideNavItemsWithExternals = useMemo( + () => [ + ...securitySideNavItems, + { + id: 'discover', + label: 'Discover', + ...getLinkProps({ appId: 'discover' }), + }, + ], + [securitySideNavItems, getLinkProps] + ); + + return sideNavItemsWithExternals; +}; + +/** + * Partitions the sideNavItems into main and footer SideNavItems + * @param sideNavItems array for all SideNavItems + * @returns `[items, footerItems]` to be used in the side navigation component + */ +export const usePartitionFooterNavItems = ( + sideNavItems: SolutionSideNavItem[] +): [SolutionSideNavItem[], SolutionSideNavItem[]] => + useMemo(() => partition((item) => !isFooterNavItem(item.id), sideNavItems), [sideNavItems]); + +/** + * Returns the selected item id, which is the root item in the links hierarchy + */ +export const useSideNavSelectedId = (sideNavItems: SolutionSideNavItem[]): string => { + const { http } = useKibana().services; + const { pathname } = useLocation(); + + const selectedId: string = useMemo(() => { + const [rootNavItem] = findItemsByPath(sideNavItems, http.basePath.prepend(pathname)); + return rootNavItem?.id ?? ''; + }, [sideNavItems, pathname, http]); + + return selectedId; +}; diff --git a/x-pack/plugins/serverless_security/public/index.ts b/x-pack/plugins/serverless_security/public/index.ts new file mode 100644 index 0000000000000..ce7b6e989f4ca --- /dev/null +++ b/x-pack/plugins/serverless_security/public/index.ts @@ -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'; diff --git a/x-pack/plugins/serverless_security/public/plugin.tsx b/x-pack/plugins/serverless_security/public/plugin.tsx new file mode 100644 index 0000000000000..2d163df117c05 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/plugin.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { + ServerlessSecurityPluginSetup, + ServerlessSecurityPluginStart, + ServerlessSecurityPluginSetupDependencies, + ServerlessSecurityPluginStartDependencies, +} from './types'; +import { SecuritySideNavigation } from './components/side_navigation'; +import { getKibanaServicesProvider } from './services'; + +export class ServerlessSecurityPlugin + implements + Plugin< + ServerlessSecurityPluginSetup, + ServerlessSecurityPluginStart, + ServerlessSecurityPluginSetupDependencies, + ServerlessSecurityPluginStartDependencies + > +{ + public setup( + _core: CoreSetup, + setupDeps: ServerlessSecurityPluginSetupDependencies + ): ServerlessSecurityPluginSetup { + setupDeps.securitySolution.setIsSidebarEnabled(false); + return {}; + } + + public start( + core: CoreStart, + startDeps: ServerlessSecurityPluginStartDependencies + ): ServerlessSecurityPluginStart { + const KibanaServicesProvider = getKibanaServicesProvider(core, startDeps); + + startDeps.serverless.setServerlessNavigation( + + + + ); + + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_security/public/services.tsx b/x-pack/plugins/serverless_security/public/services.tsx new file mode 100644 index 0000000000000..4071da69967d2 --- /dev/null +++ b/x-pack/plugins/serverless_security/public/services.tsx @@ -0,0 +1,28 @@ +/* + * 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 { CoreStart } from '@kbn/core/public'; +import React from 'react'; +import { + KibanaContextProvider, + useKibana as useKibanaReact, +} from '@kbn/kibana-react-plugin/public'; +import type { ServerlessSecurityPluginStartDependencies } from './types'; + +export type Services = CoreStart & ServerlessSecurityPluginStartDependencies; + +export const getKibanaServicesProvider = ( + core: CoreStart, + pluginsStart: ServerlessSecurityPluginStartDependencies +): React.FC => { + const services: Services = { ...core, ...pluginsStart }; + return ({ children }) => { + return {children}; + }; +}; + +export const useKibana = () => useKibanaReact(); 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..afc62a100b7a7 --- /dev/null +++ b/x-pack/plugins/serverless_security/tsconfig.json @@ -0,0 +1,27 @@ +{ + "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", + "@kbn/shared-ux-page-solution-nav", + "@kbn/security-solution-side-nav", + "@kbn/kibana-react-plugin", + ] +} diff --git a/x-pack/plugins/snapshot_restore/server/config.ts b/x-pack/plugins/snapshot_restore/server/config.ts index d259b6674391a..e2452e5b58e54 100644 --- a/x-pack/plugins/snapshot_restore/server/config.ts +++ b/x-pack/plugins/snapshot_restore/server/config.ts @@ -25,6 +25,11 @@ const schemaLatest = schema.object( slm_ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + /** + * Disables the plugin. + * Added back in 8.8. + */ + enabled: schema.boolean({ defaultValue: true }), }, { defaultValue: undefined } ); diff --git a/x-pack/plugins/synthetics/common/config.ts b/x-pack/plugins/synthetics/common/config.ts index c9c48e5878391..9da43f8bf9a08 100644 --- a/x-pack/plugins/synthetics/common/config.ts +++ b/x-pack/plugins/synthetics/common/config.ts @@ -23,6 +23,7 @@ const serviceConfig = schema.object({ const uptimeConfig = schema.object({ index: schema.maybe(schema.string()), service: schema.maybe(serviceConfig), + enabled: schema.boolean({ defaultValue: true }), }); export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/synthetics/kibana.jsonc b/x-pack/plugins/synthetics/kibana.jsonc index 3236a730f1a59..5e6f1a6f30233 100644 --- a/x-pack/plugins/synthetics/kibana.jsonc +++ b/x-pack/plugins/synthetics/kibana.jsonc @@ -15,6 +15,8 @@ "actions", "alerting", "cases", + "data", + "fleet", "embeddable", "discover", "dataViews", @@ -35,8 +37,6 @@ ], "optionalPlugins": [ "cloud", - "data", - "fleet", "home", "ml", "telemetry" @@ -52,4 +52,4 @@ "indexLifecycleManagement" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts index ec77b83977a09..2237fe29df0d5 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts @@ -28,7 +28,7 @@ import { MlPluginSetup as MlSetup } from '@kbn/ml-plugin/server'; import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; import { SecurityPluginStart } from '@kbn/security-plugin/server'; import { CloudSetup } from '@kbn/cloud-plugin/server'; -import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; +import { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'; import { FleetStartContract } from '@kbn/fleet-plugin/server'; import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import { UptimeEsClient } from '../../lib'; @@ -76,6 +76,7 @@ export interface UptimeCorePluginsSetup { usageCollection: UsageCollectionSetup; ml: MlSetup; cloud?: CloudSetup; + spaces: SpacesPluginSetup; ruleRegistry: RuleRegistryPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; taskManager: TaskManagerSetupContract; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts index 1fed640bcb4e8..58faf6ba14877 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts @@ -50,6 +50,7 @@ describe('getServiceLocations', function () { manifestUrl: 'http://local.dev', showExperimentalLocations: false, }, + enabled: true, }, // @ts-ignore logger: { @@ -101,6 +102,7 @@ describe('getServiceLocations', function () { manifestUrl: 'http://local.dev', showExperimentalLocations: false, }, + enabled: true, }, // @ts-ignore logger: { @@ -138,6 +140,7 @@ describe('getServiceLocations', function () { manifestUrl: 'http://local.dev', showExperimentalLocations: true, }, + enabled: true, }, // @ts-ignore logger: { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 02a1828c1f56c..41410f3c0aa3a 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -356,7 +356,7 @@ export class SyntheticsPrivateLocation { } ); - return agentPolicies.items; + return agentPolicies.items || []; } } diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts index 7144284e6b4f0..abb56011a14b9 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -42,7 +42,7 @@ describe('SyntheticsService', () => { } as unknown as UptimeServerSetup; const getMockedService = (locationsNum: number = 1) => { - serverMock.config = { service: { devUrl: 'http://localhost' } }; + serverMock.config = { service: { devUrl: 'http://localhost' }, enabled: true }; const service = new SyntheticsService(serverMock); const locations = times(locationsNum).map((n) => { @@ -116,6 +116,7 @@ describe('SyntheticsService', () => { username: 'dev', password: '12345', }, + enabled: true, }; const service = new SyntheticsService(serverMock); diff --git a/x-pack/plugins/upgrade_assistant/server/config.ts b/x-pack/plugins/upgrade_assistant/server/config.ts index 6202a6680708a..bf872f50b5222 100644 --- a/x-pack/plugins/upgrade_assistant/server/config.ts +++ b/x-pack/plugins/upgrade_assistant/server/config.ts @@ -12,6 +12,11 @@ import { PluginConfigDescriptor } from '@kbn/core/server'; // even for minor releases. // ------------------------------- const configSchema = schema.object({ + /** + * Disables the plugin. + */ + enabled: schema.boolean({ defaultValue: true }), + featureSet: schema.object({ /** * Ml Snapshot should only be enabled for major version upgrades. Currently this @@ -39,6 +44,9 @@ const configSchema = schema.object({ */ reindexCorrectiveActions: schema.boolean({ defaultValue: false }), }), + /** + * This config allows to hide the UI without disabling the plugin. + */ ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), diff --git a/x-pack/plugins/watcher/server/index.ts b/x-pack/plugins/watcher/server/index.ts index 0aba44ed82838..36453f571f162 100644 --- a/x-pack/plugins/watcher/server/index.ts +++ b/x-pack/plugins/watcher/server/index.ts @@ -6,6 +6,14 @@ */ import { PluginInitializerContext } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; + import { WatcherServerPlugin } from './plugin'; export const plugin = (ctx: PluginInitializerContext) => new WatcherServerPlugin(ctx); + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}; diff --git a/yarn.lock b/yarn.lock index 8eafd67699b05..8ad6781f4736c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4929,6 +4929,22 @@ version "0.0.0" uid "" +"@kbn/serverless-observability@link:x-pack/plugins/serverless_observability": + version "0.0.0" + uid "" + +"@kbn/serverless-search@link:x-pack/plugins/serverless_search": + 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 "" + "@kbn/session-notifications-plugin@link:test/plugin_functional/plugins/session_notifications": version "0.0.0" uid ""