Skip to content

Commit

Permalink
[Beats Management] [WIP] Create public resources for management plugin (
Browse files Browse the repository at this point in the history
#20864)

* Init plugin public resources.

* rename beats to beats_management

* rendering react now
  • Loading branch information
justinkambic authored and mattapperson committed Aug 14, 2018
1 parent 85d23ca commit 2510470
Show file tree
Hide file tree
Showing 66 changed files with 361 additions and 15 deletions.
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',
};
File renamed without changes.
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>
);
};
File renamed without changes.
File renamed without changes.
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

0 comments on commit 2510470

Please sign in to comment.