diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 6088ec29db9985d..f76a7d2738afbe4 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -152,7 +152,13 @@ export class CapabilitiesService { public setup(setupDeps: SetupDeps): CapabilitiesSetup { this.logger.debug('Setting up capabilities service'); - registerRoutes(setupDeps.http, this.resolveCapabilities); + registerRoutes(setupDeps.http.createRouter(''), this.resolveCapabilities); + + // The not ready server has no need for real capabilities. + // Returning the un-augmented defaults is sufficient. + setupDeps.http.notReadyServer?.registerRoutes('', (notReadyRouter) => { + registerRoutes(notReadyRouter, async () => defaultCapabilities); + }); return { registerProvider: (provider: CapabilitiesProvider) => { diff --git a/src/core/server/capabilities/routes/index.ts b/src/core/server/capabilities/routes/index.ts index a417dbfeb43a3c9..4140d75d1a1a102 100644 --- a/src/core/server/capabilities/routes/index.ts +++ b/src/core/server/capabilities/routes/index.ts @@ -7,10 +7,9 @@ */ import { CapabilitiesResolver } from '../resolve_capabilities'; -import { InternalHttpServiceSetup } from '../../http'; +import { IRouter } from '../../http'; import { registerCapabilitiesRoutes } from './resolve_capabilities'; -export function registerRoutes(http: InternalHttpServiceSetup, resolver: CapabilitiesResolver) { - const router = http.createRouter(''); +export function registerRoutes(router: IRouter, resolver: CapabilitiesResolver) { registerCapabilitiesRoutes(router, resolver); } diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index e728cb0b8247579..efdb5ed20959cbb 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -28,13 +28,17 @@ export class CoreApp { this.env = core.env; } - setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { + setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins, notReadyServerUiPlugins: UiPlugins) { this.logger.debug('Setting up core app.'); - this.registerDefaultRoutes(coreSetup, uiPlugins); + this.registerDefaultRoutes(coreSetup, uiPlugins, notReadyServerUiPlugins); this.registerStaticDirs(coreSetup); } - private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { + private registerDefaultRoutes( + coreSetup: InternalCoreSetup, + uiPlugins: UiPlugins, + notReadyServerUiPlugins: UiPlugins + ) { const httpSetup = coreSetup.http; const router = httpSetup.createRouter(''); const resources = coreSetup.httpResources.createRegistrar(router); @@ -51,6 +55,40 @@ export class CoreApp { }); }); + httpSetup.notReadyServer?.registerRoutes('', (notReadyRouter) => { + const notReadyResources = coreSetup.httpResources.createRegistrar(notReadyRouter); + + // TODO: need better mechanism for this, -OR- make setup mode render at the root instead. + notReadyRouter.get({ path: '/', validate: false }, async (context, req, res) => { + const defaultRoute = '/app/setup'; + const basePath = httpSetup.basePath.get(req); + const url = `${basePath}${defaultRoute}`; + + return res.redirected({ + headers: { + location: url, + }, + }); + }); + + registerBundleRoutes({ + router: notReadyRouter, + uiPlugins: notReadyServerUiPlugins, + packageInfo: this.env.packageInfo, + serverBasePath: coreSetup.http.basePath.serverBasePath, + }); + + notReadyResources.register( + { + path: '/app/{id}/{any*}', + validate: false, + }, + async (context, request, response) => { + return response.renderAnonymousCoreApp({ renderTarget: 'notReady' }); + } + ); + }); + // remove trailing slash catch-all router.get( { @@ -130,8 +168,16 @@ export class CoreApp { } private registerStaticDirs(coreSetup: InternalCoreSetup) { + coreSetup.http.notReadyServer?.registerStaticDir( + '/ui/{path*}', + Path.resolve(__dirname, './assets') + ); coreSetup.http.registerStaticDir('/ui/{path*}', Path.resolve(__dirname, './assets')); + coreSetup.http.notReadyServer?.registerStaticDir( + '/node_modules/@kbn/ui-framework/dist/{path*}', + fromRoot('node_modules/@kbn/ui-framework/dist') + ); coreSetup.http.registerStaticDir( '/node_modules/@kbn/ui-framework/dist/{path*}', fromRoot('node_modules/@kbn/ui-framework/dist') diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 0d28506607682ea..11dcfb54a8e1fb8 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -56,6 +56,7 @@ export class HttpService private readonly env: Env; private notReadyServer?: HttpServer; private internalSetup?: InternalHttpServiceSetup; + private notReadyServerRequestHandlerContext?: RequestHandlerContextContainer; private requestHandlerContext?: RequestHandlerContextContainer; constructor(private readonly coreContext: CoreContext) { @@ -75,6 +76,7 @@ export class HttpService } public async setup(deps: SetupDeps) { + this.notReadyServerRequestHandlerContext = deps.context.createContextContainer(); this.requestHandlerContext = deps.context.createContextContainer(); this.configSubscription = this.config$.subscribe(() => { if (this.httpServer.isListening()) { @@ -191,27 +193,29 @@ export class HttpService const notReadySetup = await this.runNotReadyServer(config); - // We cannot use the real context container since the core services may not yet be ready - const fakeContext: RequestHandlerContextContainer = new Proxy( - context.createContextContainer(), - { - get: (target, property, receiver) => { - if (property === 'createHandler') { - return Reflect.get(target, property, receiver); - } - throw new Error(`Unexpected access from fake context: ${String(property)}`); - }, - } - ); - return { + registerStaticDir: notReadySetup.registerStaticDir.bind(notReadySetup), + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( + pluginOpaqueId: PluginOpaqueId, + contextName: ContextName, + provider: RequestHandlerContextProvider + ) => + this.notReadyServerRequestHandlerContext!.registerContext( + pluginOpaqueId, + contextName, + provider + ), registerRoutes: (path: string, registerCallback: (router: IRouter) => void) => { - const router = new Router( - path, - this.log, - fakeContext.createHandler.bind(null, this.coreContext.coreId) + const enhanceHandler = this.notReadyServerRequestHandlerContext!.createHandler.bind( + null, + this.coreContext.coreId ); + const router = new Router(path, this.log, enhanceHandler); + registerCallback(router); notReadySetup.registerRouterAfterListening(router); }, diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index bbd296d6b1831a7..db2152493206fdd 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -275,11 +275,22 @@ export interface HttpServiceSetup { * Provides common {@link HttpServerInfo | information} about the running http server. */ getServerInfo: () => HttpServerInfo; + + notReadyServer?: InternalNotReadyHttpServiceSetup; } /** @internal */ export interface InternalNotReadyHttpServiceSetup { registerRoutes(path: string, callback: (router: IRouter) => void): void; + registerStaticDir: (path: string, dirPath: string) => void; + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( + pluginOpaqueId: PluginOpaqueId, + contextName: ContextName, + provider: RequestHandlerContextProvider + ) => RequestHandlerContextContainer; } /** @internal */ diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts index 44caa456e99559e..66466323eb95956 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/src/core/server/http_resources/http_resources_service.ts @@ -99,6 +99,7 @@ export class HttpResourcesService implements CoreService { + registerRoutes({ router: notReadyRouter, locale }); + }); + const router = http.createRouter(''); registerRoutes({ router, locale }); diff --git a/src/core/server/not_ready_core_route_handler_context.ts b/src/core/server/not_ready_core_route_handler_context.ts new file mode 100644 index 000000000000000..fd72cdfd7dbb631 --- /dev/null +++ b/src/core/server/not_ready_core_route_handler_context.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 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. + */ + +// eslint-disable-next-line max-classes-per-file +import { InternalCoreSetup } from './internal_types'; +import { InternalUiSettingsServiceSetup, IUiSettingsClient } from './ui_settings'; + +class NotReadyCoreUiSettingsRouteHandlerContext { + #client?: IUiSettingsClient; + constructor(private readonly uiSettingsSetup: InternalUiSettingsServiceSetup) {} + + public get client() { + if (this.#client == null) { + this.#client = this.uiSettingsSetup.defaultsClient(); + } + return this.#client; + } +} + +export class NotReadyCoreRouteHandlerContext { + readonly uiSettings: NotReadyCoreUiSettingsRouteHandlerContext; + + constructor(private readonly coreSetup: InternalCoreSetup) { + this.uiSettings = new NotReadyCoreUiSettingsRouteHandlerContext(this.coreSetup.uiSettings); + } +} diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index b59418a67219e03..7e68962a3e9b215 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -39,6 +39,7 @@ const KNOWN_MANIFEST_FIELDS = (() => { const manifestFields: { [P in keyof PluginManifest]: boolean } = { id: true, kibanaVersion: true, + type: true, version: true, configPath: true, requiredPlugins: true, @@ -182,6 +183,7 @@ export async function parseManifest( id: manifest.id, version: manifest.version, kibanaVersion: expectedKibanaVersion, + type: manifest.type ?? 'primary', configPath: manifest.configPath || snakeCase(manifest.id), requiredPlugins: Array.isArray(manifest.requiredPlugins) ? manifest.requiredPlugins : [], optionalPlugins: Array.isArray(manifest.optionalPlugins) ? manifest.optionalPlugins : [], diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 99a9aaaddcb0bdf..b2271c0cde4efd1 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -96,6 +96,7 @@ export class PluginsService implements CoreService type === 'primary')); + } + + public notReadyServerUiPlugins() { + return new Map(this.allUiPlugins().filter(([, { type }]) => type === 'notReady')); + } + + private allUiPlugins(): Array<[string, DiscoveredPlugin]> { const uiPluginNames = [...this.getTopologicallySortedPluginNames().keys()].filter( (pluginName) => this.plugins.get(pluginName)!.includesUiPlugin ); - const publicPlugins = new Map( - uiPluginNames.map((pluginName) => { - const plugin = this.plugins.get(pluginName)!; - return [ - pluginName, - { - id: pluginName, - configPath: plugin.manifest.configPath, - requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => - uiPluginNames.includes(p) - ), - optionalPlugins: plugin.manifest.optionalPlugins.filter((p) => - uiPluginNames.includes(p) - ), - requiredBundles: plugin.manifest.requiredBundles, - }, - ]; - }) - ); - return publicPlugins; + return uiPluginNames.map((pluginName) => { + const plugin = this.plugins.get(pluginName)!; + return [ + pluginName, + { + id: pluginName, + type: plugin.manifest.type, + configPath: plugin.manifest.configPath, + requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => uiPluginNames.includes(p)), + optionalPlugins: plugin.manifest.optionalPlugins.filter((p) => uiPluginNames.includes(p)), + requiredBundles: plugin.manifest.requiredBundles, + }, + ]; + }); } /** diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 0cdc806e997ef75..d6601ddfe8e3d17 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -149,6 +149,8 @@ export interface PluginManifest { */ readonly kibanaVersion: string; + readonly type: 'notReady' | 'primary'; + /** * Root {@link ConfigPath | configuration path} used by the plugin, defaults * to "id" in snake_case format. @@ -271,6 +273,8 @@ export interface DiscoveredPlugin { * duplicated here. */ readonly requiredBundles: readonly PluginName[]; + + readonly type: 'notReady' | 'primary'; } /** diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index fd4e1140d68b49c..c2b6a50763ec0ab 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -31,6 +31,7 @@ export class RenderingService { http, status, uiPlugins, + notReadyServerUiPlugins, }: RenderingSetupDeps): Promise { const router = http.createRouter(''); @@ -42,11 +43,23 @@ export class RenderingService { }); registerBootstrapRoute({ router, renderer: bootstrapRenderer }); + if (http.notReadyServer) { + const notReadyBootstrapRenderer = bootstrapRendererFactory({ + uiPlugins: notReadyServerUiPlugins, + serverBasePath: http.basePath.serverBasePath, + packageInfo: this.coreContext.env.packageInfo, + auth: http.auth, + }); + http.notReadyServer.registerRoutes('', (notReadyRouter) => { + registerBootstrapRoute({ router: notReadyRouter, renderer: notReadyBootstrapRenderer }); + }); + } + return { render: async ( request, uiSettings, - { includeUserSettings = true, vars }: IRenderOptions = {} + { includeUserSettings = true, vars, renderTarget }: IRenderOptions = {} ) => { const env = { mode: this.coreContext.env.mode, @@ -56,8 +69,8 @@ export class RenderingService { const basePath = http.basePath.get(request); const { serverBasePath, publicBaseUrl } = http.basePath; const settings = { - defaults: uiSettings.getRegistered(), - user: includeUserSettings ? await uiSettings.getUserProvided() : {}, + defaults: uiSettings?.getRegistered() ?? {}, + user: includeUserSettings && uiSettings ? await uiSettings?.getUserProvided() : {}, }; const darkMode = getSettingValue('theme:darkMode', settings, Boolean); @@ -70,6 +83,9 @@ export class RenderingService { buildNum, }); + const plugins = + renderTarget === 'notReady' ? notReadyServerUiPlugins.public : uiPlugins.public; + const metadata: RenderingMetadata = { strictCsp: http.csp.strict, uiPublicUrl: `${basePath}/ui`, @@ -95,7 +111,7 @@ export class RenderingService { externalUrl: http.externalUrl, vars: vars ?? {}, uiPlugins: await Promise.all( - [...uiPlugins.public].map(async ([id, plugin]) => ({ + [...plugins].map(async ([id, plugin]) => ({ id, plugin, config: await getUiConfig(uiPlugins, id), diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 1fa67c6b63d3e8b..699bc3e7beeb79d 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -63,10 +63,13 @@ export interface RenderingSetupDeps { http: InternalHttpServiceSetup; status: InternalStatusServiceSetup; uiPlugins: UiPlugins; + notReadyServerUiPlugins: UiPlugins; } /** @public */ export interface IRenderOptions { + renderTarget?: 'notReady' | 'primary'; + /** * Set whether to output user settings in the page metadata. * `true` by default. diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 3f553dd90678ed7..2f759304307abbc 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -48,6 +48,7 @@ import { CoreUsageDataService } from './core_usage_data'; import { DeprecationsService } from './deprecations'; import { CoreRouteHandlerContext } from './core_route_handler_context'; import { config as externalUrlConfig } from './external_url'; +import { NotReadyCoreRouteHandlerContext } from './not_ready_core_route_handler_context'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -122,7 +123,12 @@ export class Server { const environmentSetup = await this.environment.setup(); // Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph. - const { pluginTree, pluginPaths, uiPlugins } = await this.plugins.discover({ + const { + pluginTree, + pluginPaths, + uiPlugins, + notReadyServerUiPlugins, + } = await this.plugins.discover({ environment: environmentSetup, }); @@ -180,6 +186,7 @@ export class Server { http: httpSetup, status: statusSetup, uiPlugins, + notReadyServerUiPlugins, }); const httpResourcesSetup = this.httpResources.setup({ @@ -220,7 +227,7 @@ export class Server { }); this.registerCoreContext(coreSetup); - this.coreApp.setup(coreSetup, uiPlugins); + this.coreApp.setup(coreSetup, uiPlugins, notReadyServerUiPlugins); setupTransaction?.end(); return coreSetup; @@ -286,6 +293,14 @@ export class Server { } private registerCoreContext(coreSetup: InternalCoreSetup) { + coreSetup.http.notReadyServer?.registerRouteHandlerContext( + coreId, + 'core', + (context, req, res): any => { + // need better types + return new NotReadyCoreRouteHandlerContext(coreSetup); + } + ); coreSetup.http.registerRouteHandlerContext( coreId, 'core', diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index 395c95d998de308..09b307e105b877a 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -74,6 +74,8 @@ export interface InternalUiSettingsServiceSetup { * @param settings */ register(settings: Record): void; + + defaultsClient(): IUiSettingsClient; } /** @public */ @@ -96,6 +98,8 @@ export interface UiSettingsServiceSetup { * ``` */ register(settings: Record): void; + + defaultsClient(): IUiSettingsClient; } /** @public */ diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index ee0dcbea2a9aa54..9aa75e686439e3e 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -20,7 +20,7 @@ export interface UiSettingsServiceOptions { type: string; id: string; buildNum: number; - savedObjectsClient: SavedObjectsClientContract; + savedObjectsClient?: SavedObjectsClientContract; overrides?: Record; defaults?: Record; log: Logger; @@ -193,15 +193,16 @@ export class UiSettingsClient implements IUiSettingsClient { changes: Record; autoCreateOrUpgradeIfMissing?: boolean; }) { + const client = this.getSavedObjectsClient(); try { - await this.savedObjectsClient.update(this.type, this.id, changes); + await client.update(this.type, this.id, changes); } catch (error) { if (!SavedObjectsErrorHelpers.isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { throw error; } await createOrUpgradeSavedConfig({ - savedObjectsClient: this.savedObjectsClient, + savedObjectsClient: client, version: this.id, buildNum: this.buildNum, log: this.log, @@ -218,13 +219,15 @@ export class UiSettingsClient implements IUiSettingsClient { private async read({ autoCreateOrUpgradeIfMissing = true }: ReadOptions = {}): Promise< Record > { + const client = this.getSavedObjectsClient(); + try { - const resp = await this.savedObjectsClient.get>(this.type, this.id); + const resp = await client.get>(this.type, this.id); return resp.attributes; } catch (error) { if (SavedObjectsErrorHelpers.isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ - savedObjectsClient: this.savedObjectsClient, + savedObjectsClient: client, version: this.id, buildNum: this.buildNum, log: this.log, @@ -247,8 +250,18 @@ export class UiSettingsClient implements IUiSettingsClient { } private isIgnorableError(error: Error) { - const { isForbiddenError, isEsUnavailableError } = this.savedObjectsClient.errors; + const client = this.getSavedObjectsClient(); + const { isForbiddenError, isEsUnavailableError } = client.errors; return isForbiddenError(error) || isEsUnavailableError(error); } + + private getSavedObjectsClient() { + if (!this.savedObjectsClient) { + throw new Error( + `Attempted to use UI Settings Client without an instantiated Saved Objects Client.` + ); + } + return this.savedObjectsClient; + } } diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 93878d264541cdd..9731417c215a3e9 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -63,6 +63,7 @@ export class UiSettingsService return { register: this.register.bind(this), + defaultsClient: this.getDefaultsClientFactory(), }; } @@ -93,6 +94,19 @@ export class UiSettingsService }); } + private getDefaultsClientFactory() { + const { version, buildNum } = this.coreContext.env.packageInfo; + return () => + new UiSettingsClient({ + type: 'config', + id: version, + buildNum, + defaults: mapToObject(this.uiSettingsDefaults), + overrides: this.overrides, + log: this.log, + }); + } + private register(settings: Record = {}) { Object.entries(settings).forEach(([key, value]) => { if (this.uiSettingsDefaults.has(key)) { diff --git a/src/plugins/setup/README.md b/src/plugins/setup/README.md new file mode 100644 index 000000000000000..8f3c3d3897a7188 --- /dev/null +++ b/src/plugins/setup/README.md @@ -0,0 +1,3 @@ +# `setup` plugin + +`setup` is responsible for setting up your Kibana instance. diff --git a/src/plugins/setup/jest.config.js b/src/plugins/setup/jest.config.js new file mode 100644 index 000000000000000..f86284b42e2cad3 --- /dev/null +++ b/src/plugins/setup/jest.config.js @@ -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 + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/setup'], +}; diff --git a/src/plugins/setup/kibana.json b/src/plugins/setup/kibana.json new file mode 100644 index 000000000000000..9b4fd5deba9f0fd --- /dev/null +++ b/src/plugins/setup/kibana.json @@ -0,0 +1,11 @@ +{ + "id": "setup", + "version": "8.0.0", + "kibanaVersion": "kibana", + "type": "notReady", + "configPath": ["setup"], + "ui": true, + "server": true, + "requiredPlugins": [], + "requiredBundles": [] +} diff --git a/src/plugins/setup/public/app.tsx b/src/plugins/setup/public/app.tsx new file mode 100644 index 000000000000000..d1f88670de7d67d --- /dev/null +++ b/src/plugins/setup/public/app.tsx @@ -0,0 +1,68 @@ +/* + * 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 { + EuiPageTemplate, + EuiFlexGrid, + EuiButton, + EuiFlexItem, + EuiPanel, + EuiText, +} from '@elastic/eui'; +import React from 'react'; + +export const App = () => { + return ( + Do something], + }} + > + + + + Click me + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas eu tellus vitae + velit mollis fermentum quis sit amet libero. Nullam ultrices sapien quis ornare + laoreet. Maecenas pharetra elementum ultrices. Vestibulum ut augue a velit congue + posuere commodo eu nisi. Nulla facilisi. Vestibulum iaculis tempor dignissim. Fusce + turpis risus, venenatis sed ornare venenatis, facilisis id arcu. + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas eu tellus vitae + velit mollis fermentum quis sit amet libero. Nullam ultrices sapien quis ornare + laoreet. Maecenas pharetra elementum ultrices. Vestibulum ut augue a velit congue + posuere commodo eu nisi. Nulla facilisi. Vestibulum iaculis tempor dignissim. Fusce + turpis risus, venenatis sed ornare venenatis, facilisis id arcu. + + + + + + + DANGER Button + + + + + + ); +}; diff --git a/src/plugins/setup/public/index.ts b/src/plugins/setup/public/index.ts new file mode 100644 index 000000000000000..1719f4e186d24f3 --- /dev/null +++ b/src/plugins/setup/public/index.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 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 { PluginInitializerContext } from 'kibana/public'; +import { SetupPlugin } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => new SetupPlugin(); diff --git a/src/plugins/setup/public/plugin.tsx b/src/plugins/setup/public/plugin.tsx new file mode 100644 index 000000000000000..09b2f447ab018d3 --- /dev/null +++ b/src/plugins/setup/public/plugin.tsx @@ -0,0 +1,30 @@ +/* + * 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 type { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { App } from './app'; + +export class SetupPlugin implements Plugin { + constructor() {} + + public setup(core: CoreSetup) { + core.application.register({ + id: 'setup', + title: 'Setup', + chromeless: true, + mount: (params) => { + ReactDOM.render(, params.element); + return () => ReactDOM.unmountComponentAtNode(params.element); + }, + }); + } + + public start(core: CoreStart) {} +} diff --git a/src/plugins/setup/server/config.ts b/src/plugins/setup/server/config.ts new file mode 100644 index 000000000000000..4967c3ffcb54450 --- /dev/null +++ b/src/plugins/setup/server/config.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 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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +export type ConfigType = TypeOf; + +export const ConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); diff --git a/src/plugins/setup/server/index.ts b/src/plugins/setup/server/index.ts new file mode 100644 index 000000000000000..27afc6bd8c75c16 --- /dev/null +++ b/src/plugins/setup/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 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 type { TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; + +import { ConfigSchema } from './config'; +import { SetupPlugin } from './plugin'; + +export const config: PluginConfigDescriptor> = { + schema: ConfigSchema, +}; + +export const plugin = (context: PluginInitializerContext) => new SetupPlugin(context); diff --git a/src/plugins/setup/server/plugin.ts b/src/plugins/setup/server/plugin.ts new file mode 100644 index 000000000000000..e158eac5122a26f --- /dev/null +++ b/src/plugins/setup/server/plugin.ts @@ -0,0 +1,21 @@ +/* + * 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 type { Plugin, PluginInitializerContext } from 'src/core/server'; + +import type { ConfigType } from './config'; + +export class SetupPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup() {} + + public start() {} + + public stop() {} +} diff --git a/src/plugins/setup/tsconfig.json b/src/plugins/setup/tsconfig.json new file mode 100644 index 000000000000000..d211a70f12df338 --- /dev/null +++ b/src/plugins/setup/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["public/**/*", "server/**/*"], + "references": [{ "path": "../../core/tsconfig.json" }] +}