diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index de46bcfa69830..ab57fd491d748 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -178,6 +178,7 @@ /x-pack/plugins/remote_clusters/ @elastic/es-ui /x-pack/legacy/plugins/rollup/ @elastic/es-ui /x-pack/plugins/searchprofiler/ @elastic/es-ui +/x-pack/plugins/painless_lab/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui /x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui /x-pack/plugins/upgrade_assistant/ @elastic/es-ui diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 6bddbc6577853..abe7952dd5ce4 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -30,7 +30,7 @@ "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "plugins/remote_clusters", - "xpack.painlessLab": "legacy/plugins/painless_lab", + "xpack.painlessLab": "plugins/painless_lab", "xpack.reporting": ["plugins/reporting", "legacy/plugins/reporting"], "xpack.rollupJobs": "legacy/plugins/rollup", "xpack.searchProfiler": "plugins/searchprofiler", diff --git a/x-pack/index.js b/x-pack/index.js index c06e2536b7328..f3f569e021070 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -12,7 +12,6 @@ import { security } from './legacy/plugins/security'; import { ml } from './legacy/plugins/ml'; import { tilemap } from './legacy/plugins/tilemap'; import { grokdebugger } from './legacy/plugins/grokdebugger'; -import { painlessLab } from './legacy/plugins/painless_lab'; import { dashboardMode } from './legacy/plugins/dashboard_mode'; import { logstash } from './legacy/plugins/logstash'; import { beats } from './legacy/plugins/beats_management'; @@ -52,7 +51,6 @@ module.exports = function(kibana) { ml(kibana), tilemap(kibana), grokdebugger(kibana), - painlessLab(kibana), dashboardMode(kibana), logstash(kibana), beats(kibana), diff --git a/x-pack/legacy/plugins/painless_lab/index.ts b/x-pack/legacy/plugins/painless_lab/index.ts deleted file mode 100644 index 5372bbf37cf2e..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/index.ts +++ /dev/null @@ -1,32 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { resolve } from 'path'; -import { PLUGIN_ID } from './common/constants'; - -import { registerLicenseChecker } from './server/register_license_checker'; -import { registerExecuteRoute } from './server/register_execute_route'; -import { Legacy } from '../../../../kibana'; - -export const painlessLab = (kibana: any) => - new kibana.Plugin({ - id: PLUGIN_ID, - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - configPrefix: 'xpack.painless_lab', - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - devTools: [resolve(__dirname, 'public/register')], - }, - init: (server: Legacy.Server) => { - registerLicenseChecker(server); - registerExecuteRoute(server); - }, - }); diff --git a/x-pack/legacy/plugins/painless_lab/public/register.tsx b/x-pack/legacy/plugins/painless_lab/public/register.tsx deleted file mode 100644 index 1f5446bd07c27..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/public/register.tsx +++ /dev/null @@ -1,73 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -// @ts-ignore -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { npSetup, npStart } from 'ui/new_platform'; -import { registerPainless } from './register_painless'; -import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; - -npSetup.plugins.home.featureCatalogue.register({ - id: 'painlessLab', - title: i18n.translate('xpack.painlessLab.registryProviderTitle', { - defaultMessage: 'Painless Lab (beta)', - }), - description: i18n.translate('xpack.painlessLab.registryProviderDescription', { - defaultMessage: 'Simulate and debug painless code', - }), - icon: '', - path: '/app/kibana#/dev_tools/painless_lab', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, -}); - -npSetup.plugins.devTools.register({ - id: 'painless_lab', - order: 7, - title: ( - - - {i18n.translate('xpack.painlessLab.displayName', { - defaultMessage: 'Painless Lab', - })} - - - - - - - ) as any, - enableRouting: false, - disabled: false, - tooltipContent: xpackInfo.get('features.painlessLab.message'), - async mount(context, { element }) { - registerPainless(); - - const licenseCheck = { - showPage: xpackInfo.get('features.painlessLab.enableLink'), - message: xpackInfo.get('features.painlessLab.message'), - }; - - if (!licenseCheck.showPage) { - npStart.core.notifications.toasts.addDanger(licenseCheck.message); - window.location.hash = '/dev_tools'; - return () => {}; - } - - const { renderApp } = await import('./render_app'); - return renderApp(element, npStart.core); - }, -}); diff --git a/x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts b/x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts deleted file mode 100644 index 5cbed532ca56a..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/lib/check_license.ts +++ /dev/null @@ -1,46 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export function checkLicense(xpackLicenseInfo: any) { - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable the Watcher UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - enableLink: false, - enableAPIRoute: false, - message: i18n.translate('xpack.painlessLab.unavailableLicenseInformationMessage', { - defaultMessage: - 'You cannot use the Painless Lab because license information is not available at this time.', - }), - }; - } - - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - - // License is not valid - if (!isLicenseActive) { - return { - enableLink: false, - enableAPIRoute: false, - message: i18n.translate('xpack.painlessLab.licenseHasExpiredMessage', { - defaultMessage: - 'You cannot use the Painless Lab because your {licenseType} license has expired.', - values: { - licenseType, - }, - }), - }; - } - - // License is valid and active - return { - enableLink: true, - enableAPIRoute: true, - }; -} diff --git a/x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts b/x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts deleted file mode 100644 index 387a263114a6e..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/lib/license_pre_routing_factory.ts +++ /dev/null @@ -1,24 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import Boom from 'boom'; -import { PLUGIN_ID } from '../../common/constants'; -import { ServerFacade } from '../../../index_management'; - -export const licensePreRoutingFactory = (server: ServerFacade) => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN_ID).getLicenseCheckResults(); - if (!licenseCheckResults.enableAPIRoute) { - throw Boom.forbidden(licenseCheckResults.message); - } - - return null; - } - - return licensePreRouting; -}; diff --git a/x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts b/x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts deleted file mode 100644 index e4ffad9c21d60..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/register_execute_route.ts +++ /dev/null @@ -1,31 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { ServerRoute } from 'hapi'; -import { licensePreRoutingFactory } from './lib/license_pre_routing_factory'; -import { Legacy } from '../../../../../kibana'; -import { API_ROUTE_EXECUTE } from '../common/constants'; - -export function registerExecuteRoute(server: any) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: API_ROUTE_EXECUTE, - method: 'POST', - handler: (request: Legacy.Request) => { - const cluster = server.plugins.elasticsearch.getCluster('data'); - return cluster - .callWithRequest(request, 'scriptsPainlessExecute', { - body: request.payload, - }) - .catch((e: any) => { - return e.body; - }); - }, - config: { - pre: [licensePreRouting], - }, - } as ServerRoute); -} diff --git a/x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts b/x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts deleted file mode 100644 index 1ec5b33c4d9f9..0000000000000 --- a/x-pack/legacy/plugins/painless_lab/server/register_license_checker.ts +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ -// @ts-ignore -import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; -import { checkLicense } from './lib/check_license'; -import { PLUGIN_ID } from '../common/constants'; - -export function registerLicenseChecker(server: any) { - const xpackMainPlugin = server.plugins.xpack_main; - const plugin = server.plugins[PLUGIN_ID]; - - mirrorPluginStatus(xpackMainPlugin, plugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN_ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/plugins/painless_lab/common/constants.ts b/x-pack/plugins/painless_lab/common/constants.ts new file mode 100644 index 0000000000000..dfc7d8ae85a2c --- /dev/null +++ b/x-pack/plugins/painless_lab/common/constants.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { LicenseType } from '../../licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; + +export const PLUGIN = { + id: 'painlessLab', + minimumLicenseType: basicLicense, +}; + +export const API_BASE_PATH = '/api/painless_lab'; diff --git a/x-pack/plugins/painless_lab/kibana.json b/x-pack/plugins/painless_lab/kibana.json new file mode 100644 index 0000000000000..4b4ea24202846 --- /dev/null +++ b/x-pack/plugins/painless_lab/kibana.json @@ -0,0 +1,16 @@ +{ + "id": "painlessLab", + "version": "8.0.0", + "kibanaVersion": "kibana", + "requiredPlugins": [ + "devTools", + "licensing", + "home" + ], + "configPath": [ + "xpack", + "painless_lab" + ], + "server": true, + "ui": true +} diff --git a/x-pack/legacy/plugins/painless_lab/public/common/constants.tsx b/x-pack/plugins/painless_lab/public/application/common/constants.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/common/constants.tsx rename to x-pack/plugins/painless_lab/public/application/common/constants.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/common/types.ts b/x-pack/plugins/painless_lab/public/application/common/types.ts similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/common/types.ts rename to x-pack/plugins/painless_lab/public/application/common/types.ts diff --git a/x-pack/legacy/plugins/painless_lab/public/components/editor.tsx b/x-pack/plugins/painless_lab/public/application/components/editor.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/editor.tsx rename to x-pack/plugins/painless_lab/public/application/components/editor.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/main.tsx b/x-pack/plugins/painless_lab/public/application/components/main.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/main.tsx rename to x-pack/plugins/painless_lab/public/application/components/main.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/main_controls.tsx b/x-pack/plugins/painless_lab/public/application/components/main_controls.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/main_controls.tsx rename to x-pack/plugins/painless_lab/public/application/components/main_controls.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/output_pane/context_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/output_pane/context_tab.tsx rename to x-pack/plugins/painless_lab/public/application/components/output_pane/context_tab.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/output_pane/index.ts b/x-pack/plugins/painless_lab/public/application/components/output_pane/index.ts similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/output_pane/index.ts rename to x-pack/plugins/painless_lab/public/application/components/output_pane/index.ts diff --git a/x-pack/legacy/plugins/painless_lab/public/components/output_pane/output_pane.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/output_pane/output_pane.tsx rename to x-pack/plugins/painless_lab/public/application/components/output_pane/output_pane.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/output_pane/output_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/output_tab.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/output_pane/output_tab.tsx rename to x-pack/plugins/painless_lab/public/application/components/output_pane/output_tab.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/output_pane/parameters_tab.tsx b/x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/output_pane/parameters_tab.tsx rename to x-pack/plugins/painless_lab/public/application/components/output_pane/parameters_tab.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/components/request_flyout.tsx b/x-pack/plugins/painless_lab/public/application/components/request_flyout.tsx similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/components/request_flyout.tsx rename to x-pack/plugins/painless_lab/public/application/components/request_flyout.tsx diff --git a/x-pack/legacy/plugins/painless_lab/public/hooks/index.ts b/x-pack/plugins/painless_lab/public/application/hooks/index.ts similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/hooks/index.ts rename to x-pack/plugins/painless_lab/public/application/hooks/index.ts diff --git a/x-pack/legacy/plugins/painless_lab/public/hooks/use_submit_code.ts b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/hooks/use_submit_code.ts rename to x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts diff --git a/x-pack/legacy/plugins/painless_lab/public/index.scss b/x-pack/plugins/painless_lab/public/application/index.scss similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/index.scss rename to x-pack/plugins/painless_lab/public/application/index.scss diff --git a/x-pack/legacy/plugins/painless_lab/public/render_app.tsx b/x-pack/plugins/painless_lab/public/application/index.tsx similarity index 64% rename from x-pack/legacy/plugins/painless_lab/public/render_app.tsx rename to x-pack/plugins/painless_lab/public/application/index.tsx index 78102fd1002e5..d980af2779a03 100644 --- a/x-pack/legacy/plugins/painless_lab/public/render_app.tsx +++ b/x-pack/plugins/painless_lab/public/application/index.tsx @@ -5,21 +5,34 @@ */ import React from 'react'; -import { CoreStart } from 'kibana/public'; import { render, unmountComponentAtNode } from 'react-dom'; +import { CoreSetup, CoreStart } from 'kibana/public'; import { Main } from './components/main'; import { createKibanaReactContext } from '../../../../../src/plugins/kibana_react/public'; -export function renderApp(element: any, { http, i18n, uiSettings }: CoreStart) { +interface AppDependencies { + http: CoreSetup['http']; + I18nContext: CoreStart['i18n']['Context']; + uiSettings: CoreSetup['uiSettings']; +} + +export function renderApp( + element: HTMLElement | null, + { http, I18nContext, uiSettings }: AppDependencies +) { + if (!element) { + return () => undefined; + } + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ uiSettings, }); render( - +
- , + , element ); return () => unmountComponentAtNode(element); diff --git a/x-pack/legacy/plugins/painless_lab/public/lib/execute_code.ts b/x-pack/plugins/painless_lab/public/application/lib/execute_code.ts similarity index 76% rename from x-pack/legacy/plugins/painless_lab/public/lib/execute_code.ts rename to x-pack/plugins/painless_lab/public/application/lib/execute_code.ts index c8b9f2724d38b..ea7adb79cdacb 100644 --- a/x-pack/legacy/plugins/painless_lab/public/lib/execute_code.ts +++ b/x-pack/plugins/painless_lab/public/application/lib/execute_code.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { API_ROUTE_EXECUTE } from '../../common/constants'; +import { API_BASE_PATH } from '../../../common/constants'; export async function executeCode(http: any, payload: Record) { - return await http.post(API_ROUTE_EXECUTE, { + return await http.post(`${API_BASE_PATH}/execute`, { body: JSON.stringify(payload), }); } diff --git a/x-pack/legacy/plugins/painless_lab/public/lib/helpers.ts b/x-pack/plugins/painless_lab/public/application/lib/helpers.ts similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/lib/helpers.ts rename to x-pack/plugins/painless_lab/public/application/lib/helpers.ts diff --git a/x-pack/legacy/plugins/painless_lab/public/register_painless.ts b/x-pack/plugins/painless_lab/public/application/register_painless.ts similarity index 100% rename from x-pack/legacy/plugins/painless_lab/public/register_painless.ts rename to x-pack/plugins/painless_lab/public/application/register_painless.ts diff --git a/x-pack/plugins/painless_lab/public/index.scss b/x-pack/plugins/painless_lab/public/index.scss new file mode 100644 index 0000000000000..370ec54a85539 --- /dev/null +++ b/x-pack/plugins/painless_lab/public/index.scss @@ -0,0 +1 @@ +@import 'styles/index' diff --git a/x-pack/plugins/painless_lab/public/index.ts b/x-pack/plugins/painless_lab/public/index.ts new file mode 100644 index 0000000000000..f81218a4fcd96 --- /dev/null +++ b/x-pack/plugins/painless_lab/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import './styles/_index.scss'; +import { PluginInitializerContext } from 'src/core/public'; +import { PainlessLabUIPlugin } from './plugin'; + +export function plugin(ctx: PluginInitializerContext) { + return new PainlessLabUIPlugin(ctx); +} diff --git a/x-pack/plugins/painless_lab/public/plugin.tsx b/x-pack/plugins/painless_lab/public/plugin.tsx new file mode 100644 index 0000000000000..57166c5d312a7 --- /dev/null +++ b/x-pack/plugins/painless_lab/public/plugin.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Plugin, CoreStart, CoreSetup, PluginInitializerContext } from 'kibana/public'; +import { first } from 'rxjs/operators'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; +import { LICENSE_CHECK_STATE } from '../../licensing/public'; + +import { PLUGIN } from '../common/constants'; +import { PluginDependencies } from './types'; +import { registerPainless } from './application/register_painless'; + +export class PainlessLabUIPlugin implements Plugin { + constructor(ctx: PluginInitializerContext) {} + + async setup( + { http, getStartServices, uiSettings }: CoreSetup, + { devTools, home, licensing }: PluginDependencies + ) { + home.featureCatalogue.register({ + id: PLUGIN.id, + title: i18n.translate('xpack.painlessLab.registryProviderTitle', { + defaultMessage: 'Painless Lab (beta)', + }), + description: i18n.translate('xpack.painlessLab.registryProviderDescription', { + defaultMessage: 'Simulate and debug painless code.', + }), + icon: '', + path: '/app/kibana#/dev_tools/painless_lab', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, + }); + + devTools.register({ + id: 'painless_lab', + order: 7, + title: ( + + + {i18n.translate('xpack.painlessLab.displayName', { + defaultMessage: 'Painless Lab', + })} + + + + + + + ) as any, + enableRouting: false, + disabled: false, + mount: async (ctx, { element }) => { + const [core] = await getStartServices(); + + const { + i18n: { Context: I18nContext }, + notifications, + } = core; + + registerPainless(); + + const license = await licensing.license$.pipe(first()).toPromise(); + const { state, message: invalidLicenseMessage } = license.check( + PLUGIN.id, + PLUGIN.minimumLicenseType + ); + const isValidLicense = state === LICENSE_CHECK_STATE.Valid; + + if (!isValidLicense) { + notifications.toasts.addDanger(invalidLicenseMessage as string); + window.location.hash = '/dev_tools'; + return () => {}; + } + + const { renderApp } = await import('./application'); + return renderApp(element, { I18nContext, http, uiSettings }); + }, + }); + } + + async start(core: CoreStart, plugins: any) {} + + async stop() {} +} diff --git a/x-pack/plugins/painless_lab/public/styles/_index.scss b/x-pack/plugins/painless_lab/public/styles/_index.scss new file mode 100644 index 0000000000000..26f58cf82266a --- /dev/null +++ b/x-pack/plugins/painless_lab/public/styles/_index.scss @@ -0,0 +1,31 @@ + +/** + * 1. This is a very brittle way of preventing the editor and other content from disappearing + * behind the bottom bar. + */ +.painlessLabBottomBarPlaceholder { + height: $euiSize * 3; /* [1] */ +} + +.painlessLabRightPane { + border-right: none; + border-top: none; + border-bottom: none; + border-radius: 0; + padding-top: 0; + height: 100%; +} + +.painlessLabRightPane__tabs { + display: flex; + flex-direction: column; + height: 100%; + + [role="tabpanel"] { + height: 100%; + } +} + +.painlessLab__betaLabelContainer { + line-height: 0; +} diff --git a/x-pack/plugins/painless_lab/public/types.ts b/x-pack/plugins/painless_lab/public/types.ts new file mode 100644 index 0000000000000..9153f4c28de8d --- /dev/null +++ b/x-pack/plugins/painless_lab/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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { DevToolsSetup } from '../../../../src/plugins/dev_tools/public'; +import { LicensingPluginSetup } from '../../licensing/public'; + +export interface PluginDependencies { + licensing: LicensingPluginSetup; + home: HomePublicPluginSetup; + devTools: DevToolsSetup; +} diff --git a/x-pack/plugins/painless_lab/server/index.ts b/x-pack/plugins/painless_lab/server/index.ts new file mode 100644 index 0000000000000..96ea9a163deca --- /dev/null +++ b/x-pack/plugins/painless_lab/server/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; + * you may not use this file except in compliance with the Elastic License. + */ +import { PluginInitializerContext } from 'kibana/server'; +import { PainlessLabServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => { + return new PainlessLabServerPlugin(ctx); +}; diff --git a/x-pack/legacy/plugins/painless_lab/common/constants.ts b/x-pack/plugins/painless_lab/server/lib/index.ts similarity index 70% rename from x-pack/legacy/plugins/painless_lab/common/constants.ts rename to x-pack/plugins/painless_lab/server/lib/index.ts index 7179daedb338c..a9a3c61472d8c 100644 --- a/x-pack/legacy/plugins/painless_lab/common/constants.ts +++ b/x-pack/plugins/painless_lab/server/lib/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN_ID = 'painlessLab'; - -export const API_ROUTE_EXECUTE = '/api/painless_lab/execute'; +export { isEsError } from './is_es_error'; diff --git a/x-pack/plugins/painless_lab/server/lib/is_es_error.ts b/x-pack/plugins/painless_lab/server/lib/is_es_error.ts new file mode 100644 index 0000000000000..4137293cf39c0 --- /dev/null +++ b/x-pack/plugins/painless_lab/server/lib/is_es_error.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as legacyElasticsearch from 'elasticsearch'; + +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; +} diff --git a/x-pack/plugins/painless_lab/server/plugin.ts b/x-pack/plugins/painless_lab/server/plugin.ts new file mode 100644 index 0000000000000..74629a0b035ed --- /dev/null +++ b/x-pack/plugins/painless_lab/server/plugin.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; + +import { PLUGIN } from '../common/constants'; +import { License } from './services'; +import { Dependencies } from './types'; +import { registerExecuteRoute } from './routes/api'; + +export class PainlessLabServerPlugin implements Plugin { + private readonly license: License; + private readonly logger: Logger; + + constructor({ logger }: PluginInitializerContext) { + this.logger = logger.get(); + this.license = new License(); + } + + async setup({ http }: CoreSetup, { licensing }: Dependencies) { + const router = http.createRouter(); + + this.license.setup( + { + pluginId: PLUGIN.id, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate('xpack.painlessLab.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }, + { + licensing, + logger: this.logger, + } + ); + + registerExecuteRoute({ router, license: this.license }); + } + + start() {} + + stop() {} +} diff --git a/x-pack/plugins/painless_lab/server/routes/api/execute.ts b/x-pack/plugins/painless_lab/server/routes/api/execute.ts new file mode 100644 index 0000000000000..caf6ce5cb9932 --- /dev/null +++ b/x-pack/plugins/painless_lab/server/routes/api/execute.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../types'; +import { API_BASE_PATH } from '../../../common/constants'; +import { isEsError } from '../../lib'; + +const bodySchema = schema.object({ + script: schema.object({ + source: schema.string(), + params: schema.maybe(schema.recordOf(schema.string(), schema.any())), + }), + context: schema.maybe(schema.string()), + context_setup: schema.maybe( + schema.object({ + params: schema.maybe(schema.any()), + document: schema.recordOf(schema.string(), schema.any()), + index: schema.string(), + }) + ), +}); + +export function registerExecuteRoute({ router, license }: RouteDependencies) { + router.post( + { + path: `${API_BASE_PATH}/execute`, + validate: { + body: bodySchema, + }, + }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body; + + try { + const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + + const response = await callAsCurrentUser('scriptsPainlessExecute', { + body, + }); + + return res.ok({ + body: response, + }); + } catch (e) { + if (isEsError(e)) { + // Assume invalid painless script was submitted + // Return 200 with error object + return res.ok({ + body: e.body, + }); + } + return res.internalError({ body: e }); + } + }) + ); +} diff --git a/x-pack/plugins/painless_lab/server/routes/api/index.ts b/x-pack/plugins/painless_lab/server/routes/api/index.ts new file mode 100644 index 0000000000000..62f05971d59cc --- /dev/null +++ b/x-pack/plugins/painless_lab/server/routes/api/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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { registerExecuteRoute } from './execute'; diff --git a/x-pack/plugins/painless_lab/server/services/index.ts b/x-pack/plugins/painless_lab/server/services/index.ts new file mode 100644 index 0000000000000..b7a45e59549eb --- /dev/null +++ b/x-pack/plugins/painless_lab/server/services/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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { License } from './license'; diff --git a/x-pack/plugins/painless_lab/server/services/license.ts b/x-pack/plugins/painless_lab/server/services/license.ts new file mode 100644 index 0000000000000..1c9d77198f928 --- /dev/null +++ b/x-pack/plugins/painless_lab/server/services/license.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'kibana/server'; + +import { LicensingPluginSetup } from '../../../licensing/server'; +import { LicenseType, LICENSE_CHECK_STATE } from '../../../licensing/common/types'; + +export interface LicenseStatus { + isValid: boolean; + message?: string; +} + +interface SetupSettings { + pluginId: string; + minimumLicenseType: LicenseType; + defaultErrorMessage: string; +} + +export class License { + private licenseStatus: LicenseStatus = { + isValid: false, + message: 'Invalid License', + }; + + setup( + { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, + { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } + ) { + licensing.license$.subscribe(license => { + const { state, message } = license.check(pluginId, minimumLicenseType); + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + + if (hasRequiredLicense) { + this.licenseStatus = { isValid: true }; + } else { + this.licenseStatus = { + isValid: false, + message: message || defaultErrorMessage, + }; + if (message) { + logger.info(message); + } + } + }); + } + + guardApiRoute(handler: RequestHandler) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = license.getStatus(); + + if (!licenseStatus.isValid) { + return response.customError({ + body: { + message: licenseStatus.message || '', + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; + } + + getStatus() { + return this.licenseStatus; + } +} diff --git a/x-pack/plugins/painless_lab/server/types.ts b/x-pack/plugins/painless_lab/server/types.ts new file mode 100644 index 0000000000000..541a31dd175ec --- /dev/null +++ b/x-pack/plugins/painless_lab/server/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'src/core/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { License } from './services'; + +export interface RouteDependencies { + router: IRouter; + license: License; +} + +export interface Dependencies { + licensing: LicensingPluginSetup; +}