Repository navigation
HTML page rendering service #41964
Description
Activity
- addedTeam:CorePlatform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t//Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t//
on Jul 25, 2019 Pinging @elastic/kibana-platform
- changed the title
[-]View rendering in the New platform[/-][+]HTML page rendering service[/+]on Sep 6, 2019 Server-side:
In the new platform, we have one js bundle -
core, which bootstrap and load all the plugins. It means that we can serve only one page for different URLs and rely on client-side rendering to mount the required app depending on the current URL. It's different from the Legacy platform rendering, where the server needs to knowapp idto include a correct link to the application bundle.There are a few blockers on the server-side. We should either migrate them to the New platform or bridge Legacy platform API for a while (available via Hapi server object):
uiSettingsservice on the server-side. [New Platform] Expose uiSettings on server side #44994
On the client-side, we already provide data injected by the Legacy platform.capabilitiesservice on the server-side Migrate capabilities mixin to New Platform service #45393. Implemented in the Legacy platform, cannot be ported as tightly coupled withnavLinks. On the client-side, we consume data injected by the Legacy platform.nav linkson the server-side. Won't exist in the new platform. Should use Legacy platform API to inject data on the page until.
Steps:
- To introduce Rendering service in the new platform. Which is responsible to populate a Kibana page template with passed data. I didn't find any use-cases when a plugin defines a custom template, seems that we can skip this functionality for a while. The Service should be coupled to HTTP service (important for integration with Legacy platform)
interface KibanaRenderParams { strictCsp: boolean; bootstrapScriptUrl: string; ... } interface RenderingServiceSetup { renderKibanaPage(params: KibanaRenderParams): string;
- Provide an ability to respond from HttpService with rendered page.
const page = rendering.renderKibanaPage(); return response.ok(page);
- Switch legacy ui_render_mixin to the New platform Rendering service.
Client-side
The current implementation of
Application Servicelimits standalone apps URL format. The URL format is defined as/app/:appId, whereappIdis application Id to mount. Thus, any other URL format (for example, global resource/login) cannot be associated with New platform plugin.
There are several options to discuss:- Plugins declare other global(?) URLs as a part of their
Application definitionwhen registers inApplication service. As before, an application is responsible to implement internal routing to render a page.
Pros:
One point for all the mounting logic.
Easier to integrate with related services - capabilities, chrome, etc.
core.application.register({ id: 'security', globalURL: ['/login', '/logout'], async mount(context, params) { // mount is called for `/app/security`, `/login` and `/logout` return renderApp(context, params); }, }); // or register as a separate app core.application.register({ id: 'security-login', globalURL: '/login', async mount(context, params) { // mount is called for `/login` only return renderApp(context, params); }, });
- Create a separate
StandalonePage Service. Which is similar to theApplication Service, but allows a plugin to register any URL.
Pros:
More focusedApplicationandStandalonePageservices.
core.standalonePage.register({ url: '/login', async mount(context, params) { // mount is called for `/login` only return renderApp(context, params); }, });
- If an application doesn't want to be listed in ChromeUI it should configure it somehow. In the current implementation, all the apps are listed by default. Should we split registration in the application service from registration in the ChromeUI?
core.chrome.addLink({ title, icon: ... }) core.application.register({ id, mount... });
Until the new API is ready we need to recommend a workaround.
Options:- To keep rendering in the legacy platform. Plugins can migrate code to the new platform. When then need to render HTML, they request a legacy route or redirect a request to the legacy route.
- NP provides temporary API to render HTML. The platform can use Legacy infrastructure under the hood. Plugins don't depend on the implementation.
I proposed something similar to the second option here: #49102 (comment)
I think that's a practical way forward, but we should be careful about which options we expose in the NP API so we don't have too much churn from breaking it in the future.
consider adding a rendering helper #54470 (comment)
The current implementation has a couple of drawbacks:
- It's not possible to render a custom HTML, plugins have to configure response headers (csp, cache-control, content-type) manually.
https://github.com/elastic/kibana/blob/master/x-pack/plugins/security/server/routes/authentication/oidc.ts
https://github.com/elastic/kibana/blob/master/x-pack/plugins/security/server/routes/authentication/saml.ts - It's required to configure response headers even for basic cases:
return response.ok({ body: await context.core.rendering.render({ includeUserSettings: true }), headers: { 'content-security-policy': csp.header }, });
We would like to improve the situation and avoid error-prone manual configuration. It seems that this logic is more appropriate for HttpResource service #44620
- for basic use-cases when not much logic involved and we need to respond with the static "core" HTML page it can provide
interface HttpResourceSetup { registerStaticApp: ({ path: string }) => void } httpResource.registerStaticApp({ path: '/my-url'})
- for use-cases when a plugin needs to perform some computation in a route handler we could adopt HttpResource service to provide Kibana-router compatible API:
interface HttpResourceSetup { registerApp: (RouteConfig<'get'>) => KibanaResponse } httpResource.registerApp({ path: '/my-url', validate: false }, async (context, request, response) => { // response already pre-configured with necessary CSP, Content-Type, Cache-control headers // plugins can override headers except for CSP header return response.ok({ body: `<!DOCTYPE html> ...` headers: });
Are we okay with this proposal?
Can we close the current issue in favor of #44620?
Should I outline RFC for HttpResource service?
@elastic/kibana-platform- It's not possible to render a custom HTML, plugins have to configure response headers (csp, cache-control, content-type) manually.
I wonder if we couldn't improve the ergonomics of a regular app that needs a custom route (not prefixed with
/app/):- Make the server-side API easier to find by making it part of the
applicationnamespace ofCoreSetup:core.application.registerAppRoute('/login') - If an application is registered in the UI with a custom
appRouteand there is no corresponding registration on the backend, we could throw an error to guide developers.
- for use-cases when a plugin needs to perform some computation in a route handle we could adopt HttpResource service to provide Kibana-router compatible API:
interface HttpResourceSetup { registerApp: (RouteConfig<'get'>) => KibanaResponse } httpResource.registerApp({ path: '/my-url', validate: false }, async (context, request, response) => { // response already pre-configured with necessary CSP, Content-Type, Cache-control headers // plugins can override headers except for CSP header return response.ok({ body: `<!DOCTYPE html> ...` headers: });
This API seems to do very little. It feels like it should just be part of the
ResponseFactory:res.html({ html: ``, headers: {} });
- Make the server-side API easier to find by making it part of the
This API seems to do very little. It feels like it should just be part of the ResponseFactory:
I thought about it, but I decided it wasn't enough for a few (maybe minor) reasons:
htmlwill have an inconsistent interface withResponseFactory. for example, you might need to send an HTML page with an error status code.- response type is not limited by
html, but can be any type of dynamic content
kibana/x-pack/plugins/security/server/routes/authentication/oidc.ts
Lines 74 to 78 in 6cf7ece
window.location.replace( '${serverBasePath}/api/security/oidc/callback?authenticationResponseURI=' + encodeURIComponent(window.location.href) ); `, 'text/javascript',
I wonder if we couldn't improve the ergonomics of a regular app that needs a custom route (not prefixed with /app/):
Why not for both prefixed and un-prefixed apps? As I understand, a user can land on any page.
Let's move discussion to #50654
will be done in #50654
Rendering a view populates an HTML page with build artifacts and additional meta-data to bootstrap client-side core services and plugins.
Currently, Kibana supports 2 types of view rendering in ui_mixin
Also, there is an implicit concept of lightweight applications. Those applications do not have client code of the Kibana app but should have the same headers (CSP, CORS, etc.) as normal Kibana HTML page
HttpResource service (#44620) or another rendering service should provide API covering all 3 use-cases.
Known limitations
Concepts of
uiExportsandoptimizerfrom Legacy platform don't exist in the New platform. Thus a Legacy platform code cannot be accessed, loaded and bootstrapped within the New Platform core.Blocked by #18843Related:
#41981