Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Beats Management] [WIP] Create public resources for management plugin #20864

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x-pack/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { watcher } from './plugins/watcher';
import { grokdebugger } from './plugins/grokdebugger';
import { dashboardMode } from './plugins/dashboard_mode';
import { logstash } from './plugins/logstash';
import { beats } from './plugins/beats';
import { beats } from './plugins/beats_management';
import { apm } from './plugins/apm';
import { licenseManagement } from './plugins/license_management';
import { cloud } from './plugins/cloud';
Expand Down
2 changes: 2 additions & 0 deletions x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
"@kbn/test": "link:../packages/kbn-test",
"@types/boom": "^4.3.8",
"@types/chance": "^1.0.1",
"@types/history": "^4.6.2",
"@types/jest": "^22.2.3",
"@types/joi": "^10.4.0",
"@types/lodash": "^3.10.0",
"@types/pngjs": "^3.3.0",
"@types/react-router-dom": "^4.2.7",
"@types/sinon": "^5.0.1",
"abab": "^1.0.4",
"ansicolors": "0.3.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
export { PLUGIN } from './plugin';
export { INDEX_NAMES } from './index_names';
export { UNIQUENESS_ENFORCING_TYPES, ConfigurationBlockTypes } from './configuration_blocks';
export const BASE_PATH = '/management/beats_management/';
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
*/

export const PLUGIN = {
ID: 'beats',
ID: 'beats_management',
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
import { resolve } from 'path';
import { PLUGIN } from './common/constants';
import { initServerWithKibana } from './server/kibana.index';

Expand All @@ -21,10 +22,15 @@ export const configPrefix = 'xpack.beats';

export function beats(kibana: any) {
return new kibana.Plugin({
config: () => config,
configPrefix,
id: PLUGIN.ID,
require: ['kibana', 'elasticsearch', 'xpack_main'],
publicDir: resolve(__dirname, 'public'),
uiExports: {
managementSections: ['plugins/beats_management'],
},
config: () => config,
configPrefix,

init(server: any) {
initServerWithKibana(server);
},
Expand Down
22 changes: 22 additions & 0 deletions x-pack/plugins/beats_management/public/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { BASE_PATH } from '../common/constants';
import { compose } from './lib/compose/kibana';
// import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json';
// import { ThemeProvider } from 'styled-components';
import { PageRouter } from './routes';

// TODO use theme provided from parentApp when kibana supports it
import '@elastic/eui/dist/eui_theme_light.css';

function startApp(libs: any) {
libs.framework.registerManagementSection('beats', 'Beats Management', BASE_PATH);
libs.framework.render(<PageRouter />);
}

startApp(compose());
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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 { IModule, IScope } from 'angular';
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import {
BufferedKibanaServiceCall,
FrameworkAdapter,
KibanaAdapterServiceRefs,
KibanaUIConfig,
} from '../../lib';

export class KibanaFrameworkAdapter implements FrameworkAdapter {
public appState: object;
public kbnVersion?: string;

private management: any;
private adapterService: KibanaAdapterServiceProvider;
private rootComponent: React.ReactElement<any> | null = null;
private uiModule: IModule;
private routes: any;

constructor(uiModule: IModule, management: any, routes: any) {
this.adapterService = new KibanaAdapterServiceProvider();
this.management = management;
this.uiModule = uiModule;
this.routes = routes;
this.appState = {};
}

public setUISettings = (key: string, value: any) => {
this.adapterService.callOrBuffer(({ config }) => {
config.set(key, value);
});
};

public render = (component: React.ReactElement<any>) => {
this.rootComponent = component;
};

public registerManagementSection(pluginId: string, displayName: string, basePath: string) {
const registerSection = () =>
this.management.register(pluginId, {
display: displayName,
order: 30,
});
const getSection = () => this.management.getSection(pluginId);

const section = this.management.hasItem(pluginId) ? getSection() : registerSection();

section.register(pluginId, {
visible: true,
display: displayName,
order: 30,
url: `#${basePath}`,
});

this.register(this.uiModule);
}

private manageAngularLifecycle($scope: any, $route: any, elem: any) {
const lastRoute = $route.current;
const deregister = $scope.$on('$locationChangeSuccess', () => {
const currentRoute = $route.current;
// if templates are the same we are on the same route
if (lastRoute.$$route.template === currentRoute.$$route.template) {
// this prevents angular from destroying scope
$route.current = lastRoute;
}
});
$scope.$on('$destroy', () => {
if (deregister) {
deregister();
}
// manually unmount component when scope is destroyed
if (elem) {
ReactDOM.unmountComponentAtNode(elem);
}
});
}

private register = (adapterModule: IModule) => {
const adapter = this;
this.routes.when(`/management/beats_management/?`, {
template: '<beats-cm><div id="beatsReactRoot" style="flex-grow: 1;"></div></beats-cm>',
controllerAs: 'beatsManagement',
// tslint:disable-next-line: max-classes-per-file
controller: class BeatsManagementController {
constructor($scope: any, $route: any) {
$scope.$$postDigest(() => {
const elem = document.getElementById('beatsReactRoot');
ReactDOM.render(adapter.rootComponent as React.ReactElement<any>, elem);
adapter.manageAngularLifecycle($scope, $route, elem);
});
$scope.$onInit = () => {
$scope.topNavMenu = [];
};
}
},
});
};
}

// tslint:disable-next-line: max-classes-per-file
class KibanaAdapterServiceProvider {
public serviceRefs: KibanaAdapterServiceRefs | null = null;
public bufferedCalls: Array<BufferedKibanaServiceCall<KibanaAdapterServiceRefs>> = [];

public $get($rootScope: IScope, config: KibanaUIConfig) {
this.serviceRefs = {
config,
rootScope: $rootScope,
};

this.applyBufferedCalls(this.bufferedCalls);

return this;
}

public callOrBuffer(serviceCall: (serviceRefs: KibanaAdapterServiceRefs) => void) {
if (this.serviceRefs !== null) {
this.applyBufferedCalls([serviceCall]);
} else {
this.bufferedCalls.push(serviceCall);
}
}

public applyBufferedCalls(
bufferedCalls: Array<BufferedKibanaServiceCall<KibanaAdapterServiceRefs>>
) {
if (!this.serviceRefs) {
return;
}

this.serviceRefs.rootScope.$apply(() => {
bufferedCalls.forEach(serviceCall => {
if (!this.serviceRefs) {
return;
}
return serviceCall(this.serviceRefs);
});
});
}
}
29 changes: 29 additions & 0 deletions x-pack/plugins/beats_management/public/lib/compose/kibana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 'ui/autoload/all';
// @ts-ignore: path dynamic for kibana
import { management } from 'ui/management';
// @ts-ignore: path dynamic for kibana
import { uiModules } from 'ui/modules';
// @ts-ignore: path dynamic for kibana
import routes from 'ui/routes';
// @ts-ignore: path dynamic for kibana
import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
import { FrontendLibs } from '../lib';

export function compose(): FrontendLibs {
// const kbnVersion = (window as any).__KBN__.version;

const pluginUIModule = uiModules.get('app/beats_management');

const framework = new KibanaFrameworkAdapter(pluginUIModule, management, routes);

const libs: FrontendLibs = {
framework,
};
return libs;
}
62 changes: 62 additions & 0 deletions x-pack/plugins/beats_management/public/lib/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { IModule, IScope } from 'angular';
import { AxiosRequestConfig } from 'axios';
import React from 'react';

export interface FrontendLibs {
framework: FrameworkAdapter;
// api: ApiAdapter;
}

export interface FrameworkAdapter {
// Insstance vars
appState?: object;
kbnVersion?: string;
registerManagementSection(pluginId: string, displayName: string, basePath: string): void;

// Methods
setUISettings(key: string, value: any): void;
render(component: React.ReactElement<any>): void;
}

export interface FramworkAdapterConstructable {
new (uiModule: IModule): FrameworkAdapter;
}

// TODO: replace AxiosRequestConfig with something more defined
export type RequestConfig = AxiosRequestConfig;

export interface ApiAdapter {
kbnVersion: string;

get<T>(url: string, config?: RequestConfig | undefined): Promise<T>;
post(url: string, data?: any, config?: AxiosRequestConfig | undefined): Promise<object>;
delete(url: string, config?: RequestConfig | undefined): Promise<object>;
put(url: string, data?: any, config?: RequestConfig | undefined): Promise<object>;
}

export interface UiKibanaAdapterScope extends IScope {
breadcrumbs: any[];
topNavMenu: any[];
}

export interface KibanaUIConfig {
get(key: string): any;
set(key: string, value: any): Promise<boolean>;
}

export interface KibanaAdapterServiceRefs {
config: KibanaUIConfig;
rootScope: IScope;
}

export type BufferedKibanaServiceCall<ServiceRefs> = (serviceRefs: ServiceRefs) => void;

export interface Chrome {
setRootTemplate(template: string): void;
}
13 changes: 13 additions & 0 deletions x-pack/plugins/beats_management/public/pages/404.tsx
Original file line number Diff line number Diff line change
@@ -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 React from 'react';

export class NotFoundPage extends React.PureComponent {
public render() {
return <div>No content found</div>;
}
}
13 changes: 13 additions & 0 deletions x-pack/plugins/beats_management/public/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -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 React from 'react';

export class HomePage extends React.PureComponent {
public render() {
return <div>Home</div>;
}
}
22 changes: 22 additions & 0 deletions x-pack/plugins/beats_management/public/routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { HashRouter, Route, Switch } from 'react-router-dom';

import { NotFoundPage } from './pages/404';
import { HomePage } from './pages/home';

export const PageRouter: React.SFC<{}> = () => {
return (
<HashRouter basename="/management/beats_management">
<Switch>
<Route path="/" exact={true} component={HomePage} />
<Route component={NotFoundPage} />
</Switch>
</HashRouter>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

// @ts-ignore
import { createEsTestCluster } from '@kbn/test';
import { config as beatsPluginConfig, configPrefix } from '../../../../..';
// @ts-ignore
import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server';
import { config as beatsPluginConfig, configPrefix } from '../../../../../index';
import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter';
import { contractTests } from './test_contract';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { get } from 'lodash';
import { INDEX_NAMES } from '../../../../common/constants';
import { FrameworkUser } from './../framework/adapter_types';
import { FrameworkUser } from '../framework/adapter_types';

import { BeatTag } from '../../../../common/domain_types';
import { DatabaseAdapter } from '../database/adapter_types';
Expand Down
Loading