Merge branch 'master' of github.com:elastic/kibana

This commit is contained in:
LeeDr 2018-08-23 19:16:28 -05:00
commit 1cf86bfadb
52 changed files with 917 additions and 619 deletions

View file

@ -3,7 +3,11 @@
== Kibana Dashboard Only Mode
If {security} is enabled, you can use the `kibana_dashboard_only_user` built-in role to limit
what users see when they log in to {kib}.
what users see when they log in to {kib}. The `kibana_dashboard_only_user` role is
preconfigured with read-only permissions to {kib}.
IMPORTANT: You must also assign roles that grant the user appropriate access to the data indices.
For information on roles and privileges, see {xpack-ref}/authorization.html[User Authorization].
Users assigned this role are only able to see the Dashboard app in the navigation
pane. When users open a dashboard, they will have a limited visual experience.
@ -13,12 +17,7 @@ All edit and create controls are hidden.
image:management/dashboard_only_mode/images/view_only_dashboard.png["View Only Dashboard"]
To assign this role, go to *Management > Security > Users*, add or edit
a user, and add the `kibana_dashboard_only_user` role. You must assign roles
that grant the user appropriate data access. For information on roles
and privileges, see {xpack-ref}/authorization.html[User Authorization].
The `kibana_dashboard_only_user` role is
preconfigured with read-only permissions to {kib}.
a user, and add the `kibana_dashboard_only_user` role.
IMPORTANT: If you assign users the `kibana_dashboard_only_user` role, along with a role
with write permissions to {kib}, they *will* have write access,

View file

@ -21,33 +21,14 @@
import { EnvOptions } from '../../env';
interface MockEnvOptions {
config?: string;
kbnServer?: any;
mode?: EnvOptions['mode']['name'];
packageInfo?: Partial<EnvOptions['packageInfo']>;
}
export function getEnvOptions({
config,
kbnServer,
mode = 'development',
packageInfo = {},
}: MockEnvOptions = {}): EnvOptions {
export function getEnvOptions(options: Partial<EnvOptions> = {}): EnvOptions {
return {
config,
kbnServer,
mode: {
dev: mode === 'development',
name: mode,
prod: mode === 'production',
},
packageInfo: {
branch: 'some-branch',
buildNum: 1,
buildSha: 'some-sha-256',
version: 'some-version',
...packageInfo,
configs: options.configs || [],
cliArgs: {
dev: true,
...(options.cliArgs || {}),
},
isDevClusterMaster:
options.isDevClusterMaster !== undefined ? options.isDevClusterMaster : false,
};
}

View file

@ -0,0 +1,149 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`correctly creates default environment in dev mode.: env properties 1`] = `
Env {
"binDir": "/test/cwd/bin",
"cliArgs": Object {
"dev": true,
"someArg": 1,
"someOtherArg": "2",
},
"configDir": "/test/cwd/config",
"configs": Array [
"/test/cwd/config/kibana.yml",
],
"corePluginsDir": "/test/cwd/core_plugins",
"homeDir": "/test/cwd",
"isDevClusterMaster": true,
"legacy": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
"_maxListeners": undefined,
"domain": null,
},
"logDir": "/test/cwd/log",
"mode": Object {
"dev": true,
"name": "development",
"prod": false,
},
"packageInfo": Object {
"branch": "some-branch",
"buildNum": 9007199254740991,
"buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"version": "some-version",
},
"staticFilesDir": "/test/cwd/ui",
}
`;
exports[`correctly creates default environment in prod distributable mode.: env properties 1`] = `
Env {
"binDir": "/test/cwd/bin",
"cliArgs": Object {
"dev": false,
"someArg": 1,
"someOtherArg": "2",
},
"configDir": "/test/cwd/config",
"configs": Array [
"/some/other/path/some-kibana.yml",
],
"corePluginsDir": "/test/cwd/core_plugins",
"homeDir": "/test/cwd",
"isDevClusterMaster": false,
"legacy": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
"_maxListeners": undefined,
"domain": null,
},
"logDir": "/test/cwd/log",
"mode": Object {
"dev": false,
"name": "production",
"prod": true,
},
"packageInfo": Object {
"branch": "feature-v1",
"buildNum": 100,
"buildSha": "feature-v1-build-sha",
"version": "v1",
},
"staticFilesDir": "/test/cwd/ui",
}
`;
exports[`correctly creates default environment in prod non-distributable mode.: env properties 1`] = `
Env {
"binDir": "/test/cwd/bin",
"cliArgs": Object {
"dev": false,
"someArg": 1,
"someOtherArg": "2",
},
"configDir": "/test/cwd/config",
"configs": Array [
"/some/other/path/some-kibana.yml",
],
"corePluginsDir": "/test/cwd/core_plugins",
"homeDir": "/test/cwd",
"isDevClusterMaster": false,
"legacy": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
"_maxListeners": undefined,
"domain": null,
},
"logDir": "/test/cwd/log",
"mode": Object {
"dev": false,
"name": "production",
"prod": true,
},
"packageInfo": Object {
"branch": "feature-v1",
"buildNum": 9007199254740991,
"buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"version": "v1",
},
"staticFilesDir": "/test/cwd/ui",
}
`;
exports[`correctly creates environment with constructor.: env properties 1`] = `
Env {
"binDir": "/some/home/dir/bin",
"cliArgs": Object {
"dev": false,
"someArg": 1,
"someOtherArg": "2",
},
"configDir": "/some/home/dir/config",
"configs": Array [
"/some/other/path/some-kibana.yml",
],
"corePluginsDir": "/some/home/dir/core_plugins",
"homeDir": "/some/home/dir",
"isDevClusterMaster": false,
"legacy": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
"_maxListeners": undefined,
"domain": null,
},
"logDir": "/some/home/dir/log",
"mode": Object {
"dev": false,
"name": "production",
"prod": true,
},
"packageInfo": Object {
"branch": "feature-v1",
"buildNum": 100,
"buildSha": "feature-v1-build-sha",
"version": "v1",
},
"staticFilesDir": "/some/home/dir/ui",
}
`;

View file

@ -18,8 +18,13 @@
*/
/* tslint:disable max-classes-per-file */
import { BehaviorSubject } from 'rxjs';
import { first } from 'rxjs/operators';
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
jest.mock('../../../../utils/package_json', () => ({ pkg: mockPackage }));
import { schema, Type, TypeOf } from '../schema';
import { ConfigService, ObjectToRawConfigAdapter } from '..';
@ -161,21 +166,19 @@ test('tracks unhandled paths', async () => {
});
test('correctly passes context', async () => {
mockPackage.raw = {
branch: 'feature-v1',
version: 'v1',
build: {
distributable: true,
number: 100,
sha: 'feature-v1-build-sha',
},
};
const env = new Env('/kibana', getEnvOptions());
const config$ = new BehaviorSubject(new ObjectToRawConfigAdapter({ foo: {} }));
const env = new Env(
'/kibana',
getEnvOptions({
mode: 'development',
packageInfo: {
branch: 'feature-v1',
buildNum: 100,
buildSha: 'feature-v1-build-sha',
version: 'v1',
},
})
);
const configService = new ConfigService(config$, env, logger);
const configs = configService.atPath(
'foo',

View file

@ -29,76 +29,82 @@ jest.mock('path', () => ({
},
}));
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
jest.mock('../../../../utils/package_json', () => ({ pkg: mockPackage }));
import { Env } from '../env';
import { getEnvOptions } from './__mocks__/env';
test('correctly creates default environment with empty options.', () => {
const envOptions = getEnvOptions();
const defaultEnv = Env.createDefault(envOptions);
test('correctly creates default environment in dev mode.', () => {
mockPackage.raw = {
branch: 'some-branch',
version: 'some-version',
};
expect(defaultEnv.homeDir).toEqual('/test/cwd');
expect(defaultEnv.configDir).toEqual('/test/cwd/config');
expect(defaultEnv.corePluginsDir).toEqual('/test/cwd/core_plugins');
expect(defaultEnv.binDir).toEqual('/test/cwd/bin');
expect(defaultEnv.logDir).toEqual('/test/cwd/log');
expect(defaultEnv.staticFilesDir).toEqual('/test/cwd/ui');
const defaultEnv = Env.createDefault({
cliArgs: { dev: true, someArg: 1, someOtherArg: '2' },
configs: ['/test/cwd/config/kibana.yml'],
isDevClusterMaster: true,
});
expect(defaultEnv.getConfigFile()).toEqual('/test/cwd/config/kibana.yml');
expect(defaultEnv.getLegacyKbnServer()).toBeUndefined();
expect(defaultEnv.getMode()).toEqual(envOptions.mode);
expect(defaultEnv.getPackageInfo()).toEqual(envOptions.packageInfo);
expect(defaultEnv).toMatchSnapshot('env properties');
});
test('correctly creates default environment with options overrides.', () => {
const mockEnvOptions = getEnvOptions({
config: '/some/other/path/some-kibana.yml',
kbnServer: {},
mode: 'production',
packageInfo: {
branch: 'feature-v1',
buildNum: 100,
buildSha: 'feature-v1-build-sha',
version: 'v1',
test('correctly creates default environment in prod distributable mode.', () => {
mockPackage.raw = {
branch: 'feature-v1',
version: 'v1',
build: {
distributable: true,
number: 100,
sha: 'feature-v1-build-sha',
},
};
const defaultEnv = Env.createDefault({
cliArgs: { dev: false, someArg: 1, someOtherArg: '2' },
configs: ['/some/other/path/some-kibana.yml'],
isDevClusterMaster: false,
});
const defaultEnv = Env.createDefault(mockEnvOptions);
expect(defaultEnv.homeDir).toEqual('/test/cwd');
expect(defaultEnv.configDir).toEqual('/test/cwd/config');
expect(defaultEnv.corePluginsDir).toEqual('/test/cwd/core_plugins');
expect(defaultEnv.binDir).toEqual('/test/cwd/bin');
expect(defaultEnv.logDir).toEqual('/test/cwd/log');
expect(defaultEnv.staticFilesDir).toEqual('/test/cwd/ui');
expect(defaultEnv).toMatchSnapshot('env properties');
});
expect(defaultEnv.getConfigFile()).toEqual(mockEnvOptions.config);
expect(defaultEnv.getLegacyKbnServer()).toBe(mockEnvOptions.kbnServer);
expect(defaultEnv.getMode()).toEqual(mockEnvOptions.mode);
expect(defaultEnv.getPackageInfo()).toEqual(mockEnvOptions.packageInfo);
test('correctly creates default environment in prod non-distributable mode.', () => {
mockPackage.raw = {
branch: 'feature-v1',
version: 'v1',
build: {
distributable: false,
number: 100,
sha: 'feature-v1-build-sha',
},
};
const defaultEnv = Env.createDefault({
cliArgs: { dev: false, someArg: 1, someOtherArg: '2' },
configs: ['/some/other/path/some-kibana.yml'],
isDevClusterMaster: false,
});
expect(defaultEnv).toMatchSnapshot('env properties');
});
test('correctly creates environment with constructor.', () => {
const mockEnvOptions = getEnvOptions({
config: '/some/other/path/some-kibana.yml',
mode: 'production',
packageInfo: {
branch: 'feature-v1',
buildNum: 100,
buildSha: 'feature-v1-build-sha',
version: 'v1',
mockPackage.raw = {
branch: 'feature-v1',
version: 'v1',
build: {
distributable: true,
number: 100,
sha: 'feature-v1-build-sha',
},
};
const env = new Env('/some/home/dir', {
cliArgs: { dev: false, someArg: 1, someOtherArg: '2' },
configs: ['/some/other/path/some-kibana.yml'],
isDevClusterMaster: false,
});
const defaultEnv = new Env('/some/home/dir', mockEnvOptions);
expect(defaultEnv.homeDir).toEqual('/some/home/dir');
expect(defaultEnv.configDir).toEqual('/some/home/dir/config');
expect(defaultEnv.corePluginsDir).toEqual('/some/home/dir/core_plugins');
expect(defaultEnv.binDir).toEqual('/some/home/dir/bin');
expect(defaultEnv.logDir).toEqual('/some/home/dir/log');
expect(defaultEnv.staticFilesDir).toEqual('/some/home/dir/ui');
expect(defaultEnv.getConfigFile()).toEqual(mockEnvOptions.config);
expect(defaultEnv.getLegacyKbnServer()).toBeUndefined();
expect(defaultEnv.getMode()).toEqual(mockEnvOptions.mode);
expect(defaultEnv.getPackageInfo()).toEqual(mockEnvOptions.packageInfo);
expect(env).toMatchSnapshot('env properties');
});

View file

@ -138,13 +138,12 @@ export class ConfigService {
);
}
const environmentMode = this.env.getMode();
const config = ConfigClass.schema.validate(
rawConfig,
{
dev: environmentMode.dev,
prod: environmentMode.prod,
...this.env.getPackageInfo(),
dev: this.env.mode.dev,
prod: this.env.mode.prod,
...this.env.packageInfo,
},
namespace
);

View file

@ -17,10 +17,11 @@
* under the License.
*/
import { EventEmitter } from 'events';
import { resolve } from 'path';
import process from 'process';
import { LegacyKbnServer } from '../legacy_compat';
import { pkg } from '../../../utils/package_json';
interface PackageInfo {
version: string;
@ -36,11 +37,9 @@ interface EnvironmentMode {
}
export interface EnvOptions {
config?: string;
kbnServer?: any;
packageInfo: PackageInfo;
mode: EnvironmentMode;
[key: string]: any;
configs: string[];
cliArgs: Record<string, any>;
isDevClusterMaster: boolean;
}
export class Env {
@ -57,44 +56,64 @@ export class Env {
public readonly logDir: string;
public readonly staticFilesDir: string;
/**
* Information about Kibana package (version, build number etc.).
*/
public readonly packageInfo: Readonly<PackageInfo>;
/**
* Mode Kibana currently run in (development or production).
*/
public readonly mode: Readonly<EnvironmentMode>;
/**
* @internal
*/
constructor(readonly homeDir: string, private readonly options: EnvOptions) {
public readonly legacy: EventEmitter;
/**
* Arguments provided through command line.
*/
public readonly cliArgs: Readonly<Record<string, any>>;
/**
* Paths to the configuration files.
*/
public readonly configs: ReadonlyArray<string>;
/**
* Indicates that this Kibana instance is run as development Node Cluster master.
*/
public readonly isDevClusterMaster: boolean;
/**
* @internal
*/
constructor(readonly homeDir: string, options: EnvOptions) {
this.configDir = resolve(this.homeDir, 'config');
this.corePluginsDir = resolve(this.homeDir, 'core_plugins');
this.binDir = resolve(this.homeDir, 'bin');
this.logDir = resolve(this.homeDir, 'log');
this.staticFilesDir = resolve(this.homeDir, 'ui');
}
public getConfigFile() {
const defaultConfigFile = this.getDefaultConfigFile();
return this.options.config === undefined ? defaultConfigFile : this.options.config;
}
this.cliArgs = Object.freeze(options.cliArgs);
this.configs = Object.freeze(options.configs);
this.isDevClusterMaster = options.isDevClusterMaster;
/**
* @internal
*/
public getLegacyKbnServer(): LegacyKbnServer | undefined {
return this.options.kbnServer;
}
this.mode = Object.freeze<EnvironmentMode>({
dev: this.cliArgs.dev,
name: this.cliArgs.dev ? 'development' : 'production',
prod: !this.cliArgs.dev,
});
/**
* Gets information about Kibana package (version, build number etc.).
*/
public getPackageInfo() {
return this.options.packageInfo;
}
const isKibanaDistributable = pkg.build && pkg.build.distributable === true;
this.packageInfo = Object.freeze({
branch: pkg.branch,
buildNum: isKibanaDistributable ? pkg.build.number : Number.MAX_SAFE_INTEGER,
buildSha: isKibanaDistributable ? pkg.build.sha : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
version: pkg.version,
});
/**
* Gets mode Kibana currently run in (development or production).
*/
public getMode() {
return this.options.mode;
}
private getDefaultConfigFile() {
return resolve(this.configDir, 'kibana.yml');
this.legacy = new EventEmitter();
}
}

View file

@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`broadcasts server and connection options to the legacy "channel" 1`] = `
Object {
"host": "127.0.0.1",
"port": 12345,
"routes": Object {
"cors": undefined,
"payload": Object {
"maxBytes": 1024,
},
"validate": Object {
"options": Object {
"abortEarly": false,
},
},
},
"state": Object {
"strictHeader": false,
},
}
`;

View file

@ -24,7 +24,6 @@ jest.mock('fs', () => ({
}));
import Chance from 'chance';
import http from 'http';
import supertest from 'supertest';
import { Env } from '../../config';
@ -36,6 +35,7 @@ import { Router } from '../router';
const chance = new Chance();
let env: Env;
let server: HttpServer;
let config: HttpConfig;
@ -51,7 +51,8 @@ beforeEach(() => {
ssl: {},
} as HttpConfig;
server = new HttpServer(logger.get(), new Env('/kibana', getEnvOptions()));
env = new Env('/kibana', getEnvOptions());
server = new HttpServer(logger.get(), env);
});
afterEach(async () => {
@ -563,99 +564,21 @@ describe('with defined `redirectHttpFromPort`', () => {
});
});
describe('when run within legacy platform', () => {
let newPlatformProxyListenerMock: any;
beforeEach(() => {
newPlatformProxyListenerMock = {
bind: jest.fn(),
proxy: jest.fn(),
};
test('broadcasts server and connection options to the legacy "channel"', async () => {
const onConnectionListener = jest.fn();
env.legacy.on('connection', onConnectionListener);
const kbnServerMock = {
newPlatformProxyListener: newPlatformProxyListenerMock,
};
expect(onConnectionListener).not.toHaveBeenCalled();
server = new HttpServer(
logger.get(),
new Env('/kibana', getEnvOptions({ kbnServer: kbnServerMock }))
);
const router = new Router('/new');
router.get({ path: '/', validate: false }, async (req, res) => {
return res.ok({ key: 'new-platform' });
});
server.registerRouter(router);
newPlatformProxyListenerMock.proxy.mockImplementation(
(req: http.IncomingMessage, res: http.ServerResponse) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ key: `legacy-platform:${req.url}` }));
}
);
await server.start({
...config,
port: 12345,
});
test('binds proxy listener to server.', async () => {
expect(newPlatformProxyListenerMock.bind).not.toHaveBeenCalled();
expect(onConnectionListener).toHaveBeenCalledTimes(1);
await server.start(config);
expect(newPlatformProxyListenerMock.bind).toHaveBeenCalledTimes(1);
expect(newPlatformProxyListenerMock.bind).toHaveBeenCalledWith(
expect.any((http as any).Server)
);
expect(newPlatformProxyListenerMock.bind.mock.calls[0][0]).toBe(getServerListener(server));
});
test('forwards request to legacy platform if new one cannot handle it', async () => {
await server.start(config);
await supertest(getServerListener(server))
.get('/legacy')
.expect(200)
.then(res => {
expect(res.body).toEqual({ key: 'legacy-platform:/legacy' });
expect(newPlatformProxyListenerMock.proxy).toHaveBeenCalledTimes(1);
expect(newPlatformProxyListenerMock.proxy).toHaveBeenCalledWith(
expect.any((http as any).IncomingMessage),
expect.any((http as any).ServerResponse)
);
});
});
test('forwards request to legacy platform and rewrites base path if needed', async () => {
await server.start({
...config,
basePath: '/bar',
rewriteBasePath: true,
});
await supertest(getServerListener(server))
.get('/legacy')
.expect(404);
await supertest(getServerListener(server))
.get('/bar/legacy')
.expect(200)
.then(res => {
expect(res.body).toEqual({ key: 'legacy-platform:/legacy' });
expect(newPlatformProxyListenerMock.proxy).toHaveBeenCalledTimes(1);
expect(newPlatformProxyListenerMock.proxy).toHaveBeenCalledWith(
expect.any((http as any).IncomingMessage),
expect.any((http as any).ServerResponse)
);
});
});
test('do not forward request to legacy platform if new one can handle it', async () => {
await server.start(config);
await supertest(getServerListener(server))
.get('/new/')
.expect(200)
.then(res => {
expect(res.body).toEqual({ key: 'new-platform' });
expect(newPlatformProxyListenerMock.proxy).not.toHaveBeenCalled();
});
});
const [[{ options, server: rawServer }]] = onConnectionListener.mock.calls;
expect(rawServer).toBeDefined();
expect(rawServer).toBe((server as any).server);
expect(options).toMatchSnapshot();
});

View file

@ -45,7 +45,10 @@ export class HttpServer {
}
public async start(config: HttpConfig) {
this.server = createServer(getServerOptions(config));
this.log.debug('starting http server');
const serverOptions = getServerOptions(config);
this.server = createServer(serverOptions);
this.setupBasePathRewrite(this.server, config);
@ -59,32 +62,13 @@ export class HttpServer {
}
}
const legacyKbnServer = this.env.getLegacyKbnServer();
if (legacyKbnServer !== undefined) {
legacyKbnServer.newPlatformProxyListener.bind(this.server.listener);
// We register Kibana proxy middleware right before we start server to allow
// all new platform plugins register their routes, so that `legacyKbnServer`
// handles only requests that aren't handled by the new platform.
this.server.route({
handler: ({ raw: { req, res } }, responseToolkit) => {
legacyKbnServer.newPlatformProxyListener.proxy(req, res);
return responseToolkit.abandon;
},
method: '*',
options: {
payload: {
output: 'stream',
parse: false,
timeout: false,
// Having such a large value here will allow legacy routes to override
// maximum allowed payload size set in the core http server if needed.
maxBytes: Number.MAX_SAFE_INTEGER,
},
},
path: '/{p*}',
});
}
// Notify legacy compatibility layer about HTTP(S) connection providing server
// instance with connection options so that we can properly bridge core and
// the "legacy" Kibana internally.
this.env.legacy.emit('connection', {
options: serverOptions,
server: this.server,
});
await this.server.start();
@ -96,12 +80,13 @@ export class HttpServer {
}
public async stop() {
this.log.info('stopping http server');
if (this.server !== undefined) {
await this.server.stop();
this.server = undefined;
if (this.server === undefined) {
return;
}
this.log.debug('stopping http server');
await this.server.stop();
this.server = undefined;
}
private setupBasePathRewrite(server: Server, config: HttpConfig) {

View file

@ -1,3 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`correctly unbinds from the previous server. 1`] = `"Unhandled \\"error\\" event. (Error: Some error)"`;
exports[`correctly binds to the server.: proxy route options 1`] = `
Array [
Array [
Object {
"handler": [Function],
"method": "*",
"options": Object {
"payload": Object {
"maxBytes": 9007199254740991,
"output": "stream",
"parse": false,
"timeout": false,
},
},
"path": "/{p*}",
},
],
]
`;

View file

@ -1,31 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { LegacyKbnServer } from '..';
test('correctly returns `newPlatformProxyListener`.', () => {
const rawKbnServer = {
newPlatform: {
proxyListener: {},
},
};
const legacyKbnServer = new LegacyKbnServer(rawKbnServer);
expect(legacyKbnServer.newPlatformProxyListener).toBe(rawKbnServer.newPlatform.proxyListener);
});

View file

@ -17,131 +17,74 @@
* under the License.
*/
import { EventEmitter } from 'events';
import { IncomingMessage, ServerResponse } from 'http';
class MockNetServer extends EventEmitter {
public address() {
return { port: 1234, family: 'test-family', address: 'test-address' };
}
public getConnections(callback: (error: Error | null, count: number) => void) {
callback(null, 100500);
}
}
function mockNetServer() {
return new MockNetServer();
}
jest.mock('net', () => ({
createServer: jest.fn(() => mockNetServer()),
}));
import { createServer } from 'net';
import { Server as HapiServer } from 'hapi-latest';
import { Server } from 'net';
import { LegacyPlatformProxifier } from '..';
import { Env } from '../../config';
import { getEnvOptions } from '../../config/__tests__/__mocks__/env';
import { logger } from '../../logging/__mocks__';
let server: jest.Mocked<Server>;
let mockHapiServer: jest.Mocked<HapiServer>;
let root: any;
let proxifier: LegacyPlatformProxifier;
beforeEach(() => {
server = {
addListener: jest.fn(),
address: jest
.fn()
.mockReturnValue({ port: 1234, family: 'test-family', address: 'test-address' }),
getConnections: jest.fn(),
} as any;
mockHapiServer = { listener: server, route: jest.fn() } as any;
root = {
logger: {
get: jest.fn(() => ({
debug: jest.fn(),
info: jest.fn(),
})),
},
logger,
shutdown: jest.fn(),
start: jest.fn(),
} as any;
proxifier = new LegacyPlatformProxifier(root);
const env = new Env('/kibana', getEnvOptions());
proxifier = new LegacyPlatformProxifier(root, env);
env.legacy.emit('connection', {
server: mockHapiServer,
options: { someOption: 'foo', someAnotherOption: 'bar' },
});
});
test('correctly binds to the server.', () => {
const server = createServer();
jest.spyOn(server, 'addListener');
proxifier.bind(server);
expect(server.addListener).toHaveBeenCalledTimes(4);
for (const eventName of ['listening', 'error', 'clientError', 'connection']) {
expect(mockHapiServer.route.mock.calls).toMatchSnapshot('proxy route options');
expect(server.addListener).toHaveBeenCalledTimes(6);
for (const eventName of ['clientError', 'close', 'connection', 'error', 'listening', 'upgrade']) {
expect(server.addListener).toHaveBeenCalledWith(eventName, expect.any(Function));
}
});
test('correctly binds to the server and redirects its events.', () => {
const server = createServer();
proxifier.bind(server);
test('correctly redirects server events.', () => {
for (const eventName of ['clientError', 'close', 'connection', 'error', 'listening', 'upgrade']) {
expect(server.addListener).toHaveBeenCalledWith(eventName, expect.any(Function));
const eventsAndListeners = new Map(
['listening', 'error', 'clientError', 'connection'].map(eventName => {
const listener = jest.fn();
proxifier.addListener(eventName, listener);
return [eventName, listener] as [string, () => void];
})
);
for (const [eventName, listener] of eventsAndListeners) {
expect(listener).not.toHaveBeenCalled();
const listener = jest.fn();
proxifier.addListener(eventName, listener);
// Emit several events, to make sure that server is not being listened with `once`.
server.emit(eventName, 1, 2, 3, 4);
server.emit(eventName, 5, 6, 7, 8);
const [, serverListener] = server.addListener.mock.calls.find(
([serverEventName]) => serverEventName === eventName
)!;
serverListener(1, 2, 3, 4);
serverListener(5, 6, 7, 8);
expect(listener).toHaveBeenCalledTimes(2);
expect(listener).toHaveBeenCalledWith(1, 2, 3, 4);
expect(listener).toHaveBeenCalledWith(5, 6, 7, 8);
}
});
test('correctly unbinds from the previous server.', () => {
const previousServer = createServer();
proxifier.bind(previousServer);
const currentServer = createServer();
proxifier.bind(currentServer);
const eventsAndListeners = new Map(
['listening', 'error', 'clientError', 'connection'].map(eventName => {
const listener = jest.fn();
proxifier.addListener(eventName, listener);
return [eventName, listener] as [string, () => void];
})
);
// Any events from the previous server should not be forwarded.
for (const [eventName, listener] of eventsAndListeners) {
// `error` event is a special case in node, if `error` is emitted, but
// there is no listener for it error will be thrown.
if (eventName === 'error') {
expect(() =>
previousServer.emit(eventName, new Error('Some error'))
).toThrowErrorMatchingSnapshot();
} else {
previousServer.emit(eventName, 1, 2, 3, 4);
}
expect(listener).not.toHaveBeenCalled();
}
// Only events from the last server should be forwarded.
for (const [eventName, listener] of eventsAndListeners) {
expect(listener).not.toHaveBeenCalled();
currentServer.emit(eventName, 1, 2, 3, 4);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith(1, 2, 3, 4);
proxifier.removeListener(eventName, listener);
}
});
test('returns `address` from the underlying server.', () => {
expect(proxifier.address()).toBeUndefined();
proxifier.bind(createServer());
expect(proxifier.address()).toEqual({
address: 'test-address',
family: 'test-family',
@ -168,33 +111,35 @@ test('`close` shuts down the `root`.', async () => {
});
test('returns connection count from the underlying server.', () => {
server.getConnections.mockImplementation(callback => callback(null, 0));
const onGetConnectionsComplete = jest.fn();
proxifier.getConnections(onGetConnectionsComplete);
expect(onGetConnectionsComplete).toHaveBeenCalledTimes(1);
expect(onGetConnectionsComplete).toHaveBeenCalledWith(null, 0);
onGetConnectionsComplete.mockReset();
proxifier.bind(createServer());
server.getConnections.mockImplementation(callback => callback(null, 100500));
proxifier.getConnections(onGetConnectionsComplete);
expect(onGetConnectionsComplete).toHaveBeenCalledTimes(1);
expect(onGetConnectionsComplete).toHaveBeenCalledWith(null, 100500);
});
test('correctly proxies request and response objects.', () => {
test('proxy route abandons request processing and forwards it to the legacy Kibana', async () => {
const mockResponseToolkit = { response: jest.fn(), abandon: Symbol('abandon') };
const mockRequest = { raw: { req: { a: 1 }, res: { b: 2 } } };
const onRequest = jest.fn();
proxifier.addListener('request', onRequest);
const request = {} as IncomingMessage;
const response = {} as ServerResponse;
proxifier.proxy(request, response);
const [[{ handler }]] = mockHapiServer.route.mock.calls;
const response = await handler(mockRequest, mockResponseToolkit);
expect(response).toBe(mockResponseToolkit.abandon);
expect(mockResponseToolkit.response).not.toHaveBeenCalled();
// Make sure request hasn't been passed to the legacy platform.
expect(onRequest).toHaveBeenCalledTimes(1);
expect(onRequest).toHaveBeenCalledWith(request, response);
// Check that exactly same objects were passed as event arguments.
expect(onRequest.mock.calls[0][0]).toBe(request);
expect(onRequest.mock.calls[0][1]).toBe(response);
expect(onRequest).toHaveBeenCalledWith(mockRequest.raw.req, mockRequest.raw.res);
});

View file

@ -19,24 +19,18 @@
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
/** @internal */
export { LegacyPlatformProxifier } from './legacy_platform_proxifier';
/** @internal */
export { LegacyConfigToRawConfigAdapter, LegacyConfig } from './legacy_platform_config';
/** @internal */
export { LegacyKbnServer } from './legacy_kbn_server';
import {
LegacyConfig,
LegacyConfigToRawConfigAdapter,
LegacyKbnServer,
LegacyPlatformProxifier,
} from '.';
import { LegacyConfig, LegacyConfigToRawConfigAdapter, LegacyPlatformProxifier } from '.';
import { Env } from '../config';
import { Root } from '../root';
import { BasePathProxyRoot } from '../root/base_path_proxy_root';
function initEnvironment(rawKbnServer: any) {
function initEnvironment(rawKbnServer: any, isDevClusterMaster = false) {
const config: LegacyConfig = rawKbnServer.config;
const legacyConfig$ = new BehaviorSubject(config);
@ -45,12 +39,12 @@ function initEnvironment(rawKbnServer: any) {
);
const env = Env.createDefault({
kbnServer: new LegacyKbnServer(rawKbnServer),
// The defaults for the following parameters are retrieved by the legacy
// platform from the command line or from `package.json` and stored in the
// config, so we can borrow these parameters and avoid double parsing.
mode: config.get('env'),
packageInfo: config.get('pkg'),
// The core doesn't work with configs yet, everything is provided by the
// "legacy" Kibana, so we can have empty array here.
configs: [],
// `dev` is the only CLI argument we currently use.
cliArgs: { dev: config.get('env.dev') },
isDevClusterMaster,
});
return {
@ -71,12 +65,12 @@ export const injectIntoKbnServer = (rawKbnServer: any) => {
rawKbnServer.newPlatform = {
// Custom HTTP Listener that will be used within legacy platform by HapiJS server.
proxyListener: new LegacyPlatformProxifier(new Root(config$, env)),
proxyListener: new LegacyPlatformProxifier(new Root(config$, env), env),
updateConfig,
};
};
export const createBasePathProxy = (rawKbnServer: any) => {
const { env, config$ } = initEnvironment(rawKbnServer);
const { env, config$ } = initEnvironment(rawKbnServer, true /*isDevClusterMaster*/);
return new BasePathProxyRoot(config$, env);
};

View file

@ -1,34 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Represents a wrapper around legacy `kbnServer` instance that exposes only
* a subset of `kbnServer` APIs used by the new platform.
* @internal
*/
export class LegacyKbnServer {
constructor(private readonly rawKbnServer: any) {}
/**
* Custom HTTP Listener used by HapiJS server in the legacy platform.
*/
get newPlatformProxyListener() {
return this.rawKbnServer.newPlatform.proxyListener;
}
}

View file

@ -18,16 +18,29 @@
*/
import { EventEmitter } from 'events';
import { IncomingMessage, ServerResponse } from 'http';
import { Server } from 'net';
import { Server as HapiServer, ServerOptions as HapiServerOptions } from 'hapi-latest';
import { Env } from '../config';
import { Logger } from '../logging';
import { Root } from '../root';
interface ConnectionInfo {
server: HapiServer;
options: HapiServerOptions;
}
/**
* List of the server events to be forwarded to the legacy platform.
*/
const ServerEventsToForward = ['listening', 'error', 'clientError', 'connection'];
const ServerEventsToForward = [
'clientError',
'close',
'connection',
'error',
'listening',
'upgrade',
];
/**
* Represents "proxy" between legacy and current platform.
@ -38,7 +51,7 @@ export class LegacyPlatformProxifier extends EventEmitter {
private readonly log: Logger;
private server?: Server;
constructor(private readonly root: Root) {
constructor(private readonly root: Root, private readonly env: Env) {
super();
this.log = root.logger.get('legacy-platform-proxifier');
@ -56,6 +69,14 @@ export class LegacyPlatformProxifier extends EventEmitter {
] as [string, (...args: any[]) => void];
})
);
// Once core HTTP service is ready it broadcasts the internal server it relies on
// and server options that were used to create that server so that we can properly
// bridge with the "legacy" Kibana. If server isn't run (e.g. if process is managed
// by ClusterManager or optimizer) then this event will never fire.
this.env.legacy.once('connection', (connectionInfo: ConnectionInfo) =>
this.onConnection(connectionInfo)
);
}
/**
@ -116,31 +137,36 @@ export class LegacyPlatformProxifier extends EventEmitter {
}
}
/**
* Binds Http/Https server to the LegacyPlatformProxifier.
* @param server Server to bind to.
*/
public bind(server: Server) {
const oldServer = this.server;
this.server = server;
private onConnection({ server }: ConnectionInfo) {
this.server = server.listener;
for (const [eventName, eventHandler] of this.eventHandlers) {
if (oldServer !== undefined) {
oldServer.removeListener(eventName, eventHandler);
}
this.server.addListener(eventName, eventHandler);
}
}
/**
* Forwards request and response objects to the legacy platform.
* This method is used whenever new platform doesn't know how to handle the request.
* @param request Native Node request object instance.
* @param response Native Node response object instance.
*/
public proxy(request: IncomingMessage, response: ServerResponse) {
this.log.debug(`Request will be handled by proxy ${request.method}:${request.url}.`);
this.emit('request', request, response);
// We register Kibana proxy middleware right before we start server to allow
// all new platform plugins register their routes, so that `legacyProxy`
// handles only requests that aren't handled by the new platform.
server.route({
path: '/{p*}',
method: '*',
options: {
payload: {
output: 'stream',
parse: false,
timeout: false,
// Having such a large value here will allow legacy routes to override
// maximum allowed payload size set in the core http server if needed.
maxBytes: Number.MAX_SAFE_INTEGER,
},
},
handler: async ({ raw: { req, res } }, responseToolkit) => {
this.log.trace(`Request will be handled by proxy ${req.method}:${req.url}.`);
// Forward request and response objects to the legacy platform. This method
// is used whenever new platform doesn't know how to handle the request.
this.emit('request', req, res);
return responseToolkit.abandon;
},
});
}
}

View file

@ -19,7 +19,7 @@
import _ from 'lodash';
import chrome from 'ui/chrome';
import { notify } from 'ui/notify';
import { toastNotifications } from 'ui/notify';
const baseUrl = chrome.addBasePath('/api/kibana/home/tutorials');
const headers = new Headers();
@ -44,7 +44,10 @@ async function loadTutorials() {
tutorials = await response.json();
tutorialsLoaded = true;
} catch(err) {
notify.error(`Unable to load tutorials, ${err}`);
toastNotifications.addDanger({
title: 'Unable to load tutorials',
text: err.message,
});
}
}

View file

@ -22,7 +22,7 @@ import './index_header';
import './create_edit_field';
import { KbnUrlProvider } from 'ui/url';
import { IndicesEditSectionsProvider } from './edit_sections';
import { fatalError } from 'ui/notify';
import { fatalError, toastNotifications } from 'ui/notify';
import uiRoutes from 'ui/routes';
import { uiModules } from 'ui/modules';
import template from './edit_index_pattern.html';
@ -181,8 +181,7 @@ uiRoutes
uiModules.get('apps/management')
.controller('managementIndicesEdit', function (
$scope, $location, $route, config, indexPatterns, Notifier, Private, AppState, docTitle, confirmModal) {
const notify = new Notifier();
$scope, $location, $route, config, indexPatterns, Private, AppState, docTitle, confirmModal) {
const $state = $scope.state = new AppState();
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
@ -292,7 +291,7 @@ uiModules.get('apps/management')
const errorMessage = i18n.translate('kbn.management.editIndexPattern.notDateErrorMessage', {
defaultMessage: 'That field is a {fieldType} not a date.', values: { fieldType: field.type }
});
notify.error(errorMessage);
toastNotifications.addDanger(errorMessage);
return;
}
$scope.indexPattern.timeFieldName = field.name;

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,27 @@ export function cephMetricsSpecProvider() {
const moduleName = 'ceph';
return {
id: 'cephMetrics',
name: 'Ceph metrics',
name: i18n.translate('kbn.server.tutorials.cephMetrics.nameTitle', {
defaultMessage: 'Ceph metrics',
}),
isBeta: true,
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from the Ceph server.',
longDescription: 'The `ceph` Metricbeat module fetches internal metrics from Ceph.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-ceph.html).',
shortDescription: i18n.translate('kbn.server.tutorials.cephMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from the Ceph server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.cephMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `ceph` Metricbeat module fetches internal metrics from Ceph. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-ceph.html',
},
}),
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.cephMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,27 @@ export function couchbaseMetricsSpecProvider() {
const moduleName = 'couchbase';
return {
id: 'couchbaseMetrics',
name: 'Couchbase metrics',
name: i18n.translate('kbn.server.tutorials.couchbaseMetrics.nameTitle', {
defaultMessage: 'Couchbase metrics',
}),
isBeta: true,
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from Couchbase.',
longDescription: 'The `couchbase` Metricbeat module fetches internal metrics from Couchbase.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-couchbase.html).',
shortDescription: i18n.translate('kbn.server.tutorials.couchbaseMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from Couchbase.',
}),
longDescription: i18n.translate('kbn.server.tutorials.couchbaseMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `couchbase` Metricbeat module fetches internal metrics from Couchbase. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-couchbase.html',
},
}),
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.couchbaseMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,17 +25,29 @@ export function dockerMetricsSpecProvider() {
const moduleName = 'docker';
return {
id: 'dockerMetrics',
name: 'Docker metrics',
name: i18n.translate('kbn.server.tutorials.dockerMetrics.nameTitle', {
defaultMessage: 'Docker metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch metrics about your Docker containers.',
longDescription: 'The `docker` Metricbeat module fetches metrics from the Docker server.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-docker.html).',
shortDescription: i18n.translate('kbn.server.tutorials.dockerMetrics.shortDescription', {
defaultMessage: 'Fetch metrics about your Docker containers.',
}),
longDescription: i18n.translate('kbn.server.tutorials.dockerMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `docker` Metricbeat module fetches metrics from the Docker server. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-docker.html',
},
}),
euiIconType: 'logoDocker',
artifacts: {
dashboards: [
{
id: 'AV4REOpp5NkDleZmzKkE',
linkLabel: 'Docker metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.dockerMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'Docker metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,27 @@ export function dropwizardMetricsSpecProvider() {
const moduleName = 'dropwizard';
return {
id: 'dropwizardMetrics',
name: 'Dropwizard metrics',
name: i18n.translate('kbn.server.tutorials.dropwizardMetrics.nameTitle', {
defaultMessage: 'Dropwizard metrics',
}),
isBeta: true,
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from Dropwizard Java application.',
longDescription: 'The `dropwizard` Metricbeat module fetches internal metrics from Dropwizard Java Application.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-dropwizard.html).',
shortDescription: i18n.translate('kbn.server.tutorials.dropwizardMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from Dropwizard Java application.',
}),
longDescription: i18n.translate('kbn.server.tutorials.dropwizardMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `dropwizard` Metricbeat module fetches internal metrics from Dropwizard Java Application. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-dropwizard.html',
},
}),
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.dropwizardMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/filebeat_instructions';
@ -27,16 +28,28 @@ export function elasticsearchLogsSpecProvider() {
const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'];
return {
id: 'elasticsearchLogs',
name: 'Elasticsearch logs',
name: i18n.translate('kbn.server.tutorials.elasticsearchLogs.nameTitle', {
defaultMessage: 'Elasticsearch logs',
}),
category: TUTORIAL_CATEGORY.LOGGING,
isBeta: true,
shortDescription: 'Collect and parse logs created by Elasticsearch.',
longDescription: 'The `elasticsearch` Filebeat module parses logs created by Elasticsearch.' +
' [Learn more]({config.docs.beats.filebeat}/filebeat-module-elasticsearch.html).',
shortDescription: i18n.translate('kbn.server.tutorials.elasticsearchLogs.shortDescription', {
defaultMessage: 'Collect and parse logs created by Elasticsearch.',
}),
longDescription: i18n.translate('kbn.server.tutorials.elasticsearchLogs.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `elasticsearch` Filebeat module parses logs created by Elasticsearch. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-elasticsearch.html',
},
}),
euiIconType: 'logoElasticsearch',
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.elasticsearchLogs.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,17 +25,29 @@ export function mongodbMetricsSpecProvider() {
const moduleName = 'mongodb';
return {
id: 'mongodbMetrics',
name: 'MongoDB metrics',
name: i18n.translate('kbn.server.tutorials.mongodbMetrics.nameTitle', {
defaultMessage: 'MongoDB metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from MongoDB.',
longDescription: 'The `mongodb` Metricbeat module fetches internal metrics from the MongoDB server.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-mongodb.html).',
shortDescription: i18n.translate('kbn.server.tutorials.mongodbMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from MongoDB.',
}),
longDescription: i18n.translate('kbn.server.tutorials.mongodbMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `mongodb` Metricbeat module fetches internal metrics from the MongoDB server. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-mongodb.html',
},
}),
//euiIconType: 'logoMongoDB',
artifacts: {
dashboards: [
{
id: 'Metricbeat-MongoDB',
linkLabel: 'MongoDB metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.mongodbMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'MongoDB metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,27 @@ export function muninMetricsSpecProvider() {
const moduleName = 'munin';
return {
id: 'muninMetrics',
name: 'Munin metrics',
name: i18n.translate('kbn.server.tutorials.muninMetrics.nameTitle', {
defaultMessage: 'Munin metrics',
}),
isBeta: true,
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from the Munin server.',
longDescription: 'The `munin` Metricbeat module fetches internal metrics from Munin.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-munin.html).',
shortDescription: i18n.translate('kbn.server.tutorials.muninMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from the Munin server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.muninMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `munin` Metricbeat module fetches internal metrics from Munin. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-munin.html',
},
}),
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.muninMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/filebeat_instructions';
@ -27,17 +28,29 @@ export function mysqlLogsSpecProvider() {
const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'];
return {
id: 'mysqlLogs',
name: 'MySQL logs',
name: i18n.translate('kbn.server.tutorials.mysqlLogs.nameTitle', {
defaultMessage: 'MySQL logs',
}),
category: TUTORIAL_CATEGORY.LOGGING,
shortDescription: 'Collect and parse error and slow logs created by MySQL.',
longDescription: 'The `mysql` Filebeat module parses error and slow logs created by MySQL.' +
' [Learn more]({config.docs.beats.filebeat}/filebeat-module-mysql.html).',
shortDescription: i18n.translate('kbn.server.tutorials.mysqlLogs.shortDescription', {
defaultMessage: 'Collect and parse error and slow logs created by MySQL.',
}),
longDescription: i18n.translate('kbn.server.tutorials.mysqlLogs.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `mysql` Filebeat module parses error and slow logs created by MySQL. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-mysql.html',
},
}),
euiIconType: 'logoMySQL',
artifacts: {
dashboards: [
{
id: 'Filebeat-MySQL-Dashboard',
linkLabel: 'MySQL logs dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.mysqlLogs.artifacts.dashboards.linkLabel', {
defaultMessage: 'MySQL logs dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,17 +25,29 @@ export function mysqlMetricsSpecProvider() {
const moduleName = 'mysql';
return {
id: 'mysqlMetrics',
name: 'MySQL metrics',
name: i18n.translate('kbn.server.tutorials.mysqlMetrics.nameTitle', {
defaultMessage: 'MySQL metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from MySQL.',
longDescription: 'The `mysql` Metricbeat module fetches internal metrics from the MySQL server.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-mysql.html).',
shortDescription: i18n.translate('kbn.server.tutorials.mysqlMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from MySQL.',
}),
longDescription: i18n.translate('kbn.server.tutorials.mysqlMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `mysql` Metricbeat module fetches internal metrics from the MySQL server. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-mysql.html',
},
}),
euiIconType: 'logoMySQL',
artifacts: {
dashboards: [
{
id: '66881e90-0006-11e7-bf7f-c9acc3d3e306',
linkLabel: 'MySQL metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.mysqlMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'MySQL metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/filebeat_instructions';
@ -27,17 +28,29 @@ export function nginxLogsSpecProvider() {
const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'];
return {
id: 'nginxLogs',
name: 'Nginx logs',
name: i18n.translate('kbn.server.tutorials.nginxLogs.nameTitle', {
defaultMessage: 'Nginx logs',
}),
category: TUTORIAL_CATEGORY.LOGGING,
shortDescription: 'Collect and parse access and error logs created by the Nginx HTTP server.',
longDescription: 'The `nginx` Filebeat module parses access and error logs created by the Nginx HTTP server.' +
' [Learn more]({config.docs.beats.filebeat}/filebeat-module-nginx.html).',
shortDescription: i18n.translate('kbn.server.tutorials.nginxLogs.shortDescription', {
defaultMessage: 'Collect and parse access and error logs created by the Nginx HTTP server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.nginxLogs.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `nginx` Filebeat module parses access and error logs created by the Nginx HTTP server. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-nginx.html',
},
}),
euiIconType: 'logoNginx',
artifacts: {
dashboards: [
{
id: '55a9e6e0-a29e-11e7-928f-5dbe6f6f5519',
linkLabel: 'Nginx logs dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.nginxLogs.artifacts.dashboards.linkLabel', {
defaultMessage: 'Nginx logs dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,20 +25,33 @@ export function nginxMetricsSpecProvider() {
const moduleName = 'nginx';
return {
id: 'nginxMetrics',
name: 'Nginx metrics',
name: i18n.translate('kbn.server.tutorials.nginxMetrics.nameTitle', {
defaultMessage: 'Nginx metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from the Nginx HTTP server.',
longDescription: 'The `nginx` Metricbeat module fetches internal metrics from the Nginx HTTP server.' +
' The module scrapes the server status data from the web page generated by the' +
' [ngx_http_stub_status_module](http://nginx.org/en/docs/http/ngx_http_stub_status_module.html),' +
' which must be enabled in your Nginx installation.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-nginx.html).',
shortDescription: i18n.translate('kbn.server.tutorials.nginxMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from the Nginx HTTP server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.nginxMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `nginx` Metricbeat module fetches internal metrics from the Nginx HTTP server. \
The module scrapes the server status data from the web page generated by the \
{statusModuleLink}, \
which must be enabled in your Nginx installation. \
[Learn more]({learnMoreLink}).',
values: {
statusModuleLink: '[ngx_http_stub_status_module](http://nginx.org/en/docs/http/ngx_http_stub_status_module.html)',
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-nginx.html',
},
}),
euiIconType: 'logoNginx',
artifacts: {
dashboards: [
{
id: '023d2930-f1a5-11e7-a9ef-93c69af7b129',
linkLabel: 'Nginx metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.nginxMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'Nginx metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,17 +25,29 @@ export function redisMetricsSpecProvider() {
const moduleName = 'redis';
return {
id: 'redisMetrics',
name: 'Redis metrics',
name: i18n.translate('kbn.server.tutorials.redisMetrics.nameTitle', {
defaultMessage: 'Redis metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from Redis.',
longDescription: 'The `redis` Metricbeat module fetches internal metrics from the Redis server.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-redis.html).',
shortDescription: i18n.translate('kbn.server.tutorials.redisMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from Redis.',
}),
longDescription: i18n.translate('kbn.server.tutorials.redisMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `redis` Metricbeat module fetches internal metrics from the Redis server. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-redis.html',
},
}),
euiIconType: 'logoRedis',
artifacts: {
dashboards: [
{
id: 'AV4YjZ5pux-M-tCAunxK',
linkLabel: 'Redis metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.redisMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'Redis metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/filebeat_instructions';
@ -27,17 +28,29 @@ export function systemLogsSpecProvider() {
const platforms = ['OSX', 'DEB', 'RPM'];
return {
id: 'systemLogs',
name: 'System logs',
name: i18n.translate('kbn.server.tutorials.systemLogs.nameTitle', {
defaultMessage: 'System logs',
}),
category: TUTORIAL_CATEGORY.LOGGING,
shortDescription: 'Collect and parse logs written by the local Syslog server.',
longDescription: 'The `system` Filebeat module collects and parses logs created by the system logging service of common ' +
' Unix/Linux based distributions. This module is not available on Windows.' +
' [Learn more]({config.docs.beats.filebeat}/filebeat-module-system.html).',
shortDescription: i18n.translate('kbn.server.tutorials.systemLogs.shortDescription', {
defaultMessage: 'Collect and parse logs written by the local Syslog server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.systemLogs.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `system` Filebeat module collects and parses logs created by the system logging service of common \
Unix/Linux based distributions. This module is not available on Windows. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-system.html',
},
}),
artifacts: {
dashboards: [
{
id: 'Filebeat-syslog-dashboard',
linkLabel: 'System logs dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.systemLogs.artifacts.dashboards.linkLabel', {
defaultMessage: 'System logs dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,17 +25,29 @@ export function systemMetricsSpecProvider() {
const moduleName = 'system';
return {
id: 'systemMetrics',
name: 'System metrics',
name: i18n.translate('kbn.server.tutorials.systemMetrics.nameTitle', {
defaultMessage: 'System metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Collect CPU, memory, network, and disk statistics from the host.',
longDescription: 'The `system` Metricbeat module collects CPU, memory, network, and disk statistics from the host.' +
' It collects system wide statistics and statistics per process and filesystem.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-system.html).',
shortDescription: i18n.translate('kbn.server.tutorials.systemMetrics.shortDescription', {
defaultMessage: 'Collect CPU, memory, network, and disk statistics from the host.',
}),
longDescription: i18n.translate('kbn.server.tutorials.systemMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `system` Metricbeat module collects CPU, memory, network, and disk statistics from the host. \
It collects system wide statistics and statistics per process and filesystem. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-system.html',
},
}),
artifacts: {
dashboards: [
{
id: 'Metricbeat-system-overview',
linkLabel: 'System metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.systemMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'System metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/filebeat_instructions';
@ -27,17 +28,29 @@ export function traefikLogsSpecProvider() {
const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'];
return {
id: 'traefikLogs',
name: 'Traefik logs',
name: i18n.translate('kbn.server.tutorials.traefikLogs.nameTitle', {
defaultMessage: 'Traefik logs',
}),
category: TUTORIAL_CATEGORY.LOGGING,
shortDescription: 'Collect and parse access logs created by the Traefik Proxy.',
longDescription: 'The `traefik` Filebeat module parses access logs created by Traefik.' +
' [Learn more]({config.docs.beats.filebeat}/filebeat-module-traefik.html).',
shortDescription: i18n.translate('kbn.server.tutorials.traefikLogs.shortDescription', {
defaultMessage: 'Collect and parse access logs created by the Traefik Proxy.',
}),
longDescription: i18n.translate('kbn.server.tutorials.traefikLogs.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `traefik` Filebeat module parses access logs created by Traefik. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-traefik.html',
},
}),
//euiIconType: 'logoTraefik',
artifacts: {
dashboards: [
{
id: 'Filebeat-Traefik-Dashboard',
linkLabel: 'Traefik logs dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.traefikLogs.artifacts.dashboards.linkLabel', {
defaultMessage: 'Traefik logs dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,18 +25,30 @@ export function uwsgiMetricsSpecProvider() {
const moduleName = 'uwsgi';
return {
id: 'uwsgiMetrics',
name: 'uWSGI metrics',
name: i18n.translate('kbn.server.tutorials.uwsgiMetrics.nameTitle', {
defaultMessage: 'uWSGI metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from the uWSGI server.',
longDescription: 'The `uwsgi` Metricbeat module fetches internal metrics from the uWSGI server.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-uwsgi.html).',
shortDescription: i18n.translate('kbn.server.tutorials.uwsgiMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from the uWSGI server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.uwsgiMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `uwsgi` Metricbeat module fetches internal metrics from the uWSGI server. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-uwsgi.html',
},
}),
//euiIconType: 'logouWSGI',
isBeta: true,
artifacts: {
dashboards: [
{
id: '32fca290-f0af-11e7-b9ff-9f96241065de',
linkLabel: 'uWSGI metrics dashboard',
linkLabel: i18n.translate('kbn.server.tutorials.uwsgiMetrics.artifacts.dashboards.linkLabel', {
defaultMessage: 'uWSGI metrics dashboard',
}),
isOverview: true
}
],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,27 @@ export function vSphereMetricsSpecProvider() {
const moduleName = 'vsphere';
return {
id: 'vsphereMetrics',
name: 'vSphere metrics',
name: i18n.translate('kbn.server.tutorials.vsphereMetrics.nameTitle', {
defaultMessage: 'vSphere metrics',
}),
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from vSphere.',
longDescription: 'The `vsphere` Metricbeat module fetches internal metrics from a vSphere cluster.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-vsphere.html).',
shortDescription: i18n.translate('kbn.server.tutorials.vsphereMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from vSphere.',
}),
longDescription: i18n.translate('kbn.server.tutorials.vsphereMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `vsphere` Metricbeat module fetches internal metrics from a vSphere cluster. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-vsphere.html',
},
}),
//euiIconType: 'logoVSphere',
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.vsphereMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,27 @@ export function windowsMetricsSpecProvider() {
const moduleName = 'windows';
return {
id: 'windowsMetrics',
name: 'Windows metrics',
name: i18n.translate('kbn.server.tutorials.windowsMetrics.nameTitle', {
defaultMessage: 'Windows metrics',
}),
isBeta: true,
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch internal metrics from Windows.',
longDescription: 'The `windows` Metricbeat module fetches internal metrics from Windows.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-windows.html).',
shortDescription: i18n.translate('kbn.server.tutorials.windowsMetrics.shortDescription', {
defaultMessage: 'Fetch internal metrics from Windows.',
}),
longDescription: i18n.translate('kbn.server.tutorials.windowsMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `windows` Metricbeat module fetches internal metrics from Windows. \
[Learn more]({learnMoreLink}).',
values: {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-windows.html',
},
}),
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.windowsMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
@ -24,15 +25,28 @@ export function zookeeperMetricsSpecProvider() {
const moduleName = 'zookeeper';
return {
id: moduleName + 'Metrics',
name: 'Zookeeper metrics',
name: i18n.translate('kbn.server.tutorials.zookeeperMetrics.nameTitle', {
defaultMessage: 'Zookeeper metrics',
}),
isBeta: true,
category: TUTORIAL_CATEGORY.METRICS,
shortDescription: 'Fetch interal metrics from a Zookeeper server.',
longDescription: 'The `' + moduleName + '` Metricbeat module fetches internal metrics from a Zookeeper server.' +
' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-' + moduleName + '.html).',
shortDescription: i18n.translate('kbn.server.tutorials.zookeeperMetrics.shortDescription', {
defaultMessage: 'Fetch interal metrics from a Zookeeper server.',
}),
longDescription: i18n.translate('kbn.server.tutorials.zookeeperMetrics.longDescription', {
// eslint-disable-next-line no-multi-str
defaultMessage: 'The `{moduleName}` Metricbeat module fetches internal metrics from a Zookeeper server. \
[Learn more]({learnMoreLink}).',
values: {
moduleName,
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-' + moduleName + '.html',
},
}),
artifacts: {
application: {
label: 'Discover',
label: i18n.translate('kbn.server.tutorials.zookeeperMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover'
},
dashboards: [],

View file

@ -20,7 +20,7 @@
import _ from 'lodash';
import chrome from 'ui/chrome';
import { notify } from 'ui/notify';
import { toastNotifications } from 'ui/notify';
import { i18n } from '@kbn/i18n';
// Module-level error returned by notify.error
@ -130,7 +130,7 @@ async function loadStatus(fetchFn = fetchData) {
},
);
errorNotif = notify.error(serverIsDownErrorMessage);
errorNotif = toastNotifications.addDanger(serverIsDownErrorMessage);
return e;
}
@ -144,7 +144,7 @@ async function loadStatus(fetchFn = fetchData) {
},
);
errorNotif = notify.error(serverStatusCodeErrorMessage);
errorNotif = toastNotifications.addDanger(serverStatusCodeErrorMessage);
return;
}

View file

@ -23,6 +23,7 @@ import fixtures from 'fixtures/fake_hierarchical_data';
import sinon from 'sinon';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import { toastNotifications } from 'ui/notify';
import { VisProvider } from '../../../vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { BuildHierarchicalDataProvider } from '../build_hierarchical_data';
@ -276,6 +277,9 @@ describe('buildHierarchicalData', function () {
let results;
beforeEach(function () {
// Clear existing toasts.
toastNotifications.list.splice(0);
let id = 1;
vis = new Vis(indexPattern, {
type: 'pie',
@ -299,10 +303,11 @@ describe('buildHierarchicalData', function () {
});
it('should set the hits attribute for the results', function () {
const errCall = Notifier.prototype.error.getCall(0);
expect(errCall).to.be.ok();
expect(errCall.args[0]).to.contain('not supported');
// Ideally, buildHierarchicalData shouldn't be tightly coupled to toastNotifications. Instead,
// it should notify its consumer of this error and the consumer should be responsible for
// notifying the user. This test verifies the side effect of the error until we can remove
// this coupling.
expect(toastNotifications.list).to.have.length(1);
expect(results).to.have.property('slices');
expect(results).to.have.property('names');
expect(results.names).to.have.length(2);

View file

@ -18,6 +18,7 @@
*/
import _ from 'lodash';
import { toastNotifications } from 'ui/notify';
import { extractBuckets } from './_extract_buckets';
import { createRawData } from './_create_raw_data';
import { arrayToLinkedList } from './_array_to_linked_list';
@ -25,15 +26,10 @@ import AggConfigResult from '../../vis/agg_config_result';
import { AggResponseHierarchicalBuildSplitProvider } from './_build_split';
import { HierarchicalTooltipFormatterProvider } from './_hierarchical_tooltip_formatter';
export function BuildHierarchicalDataProvider(Private, Notifier) {
export function BuildHierarchicalDataProvider(Private) {
const buildSplit = Private(AggResponseHierarchicalBuildSplitProvider);
const tooltipFormatter = Private(HierarchicalTooltipFormatterProvider);
const notify = new Notifier({
location: 'Pie chart response converter'
});
return function (vis, resp) {
// Create a reference to the buckets
let buckets = vis.getAggConfig().bySchemaGroup.buckets;
@ -73,7 +69,10 @@ export function BuildHierarchicalDataProvider(Private, Notifier) {
const aggData = resp.aggregations ? resp.aggregations[firstAgg.id] : null;
if (!firstAgg._next && firstAgg.schema.name === 'split') {
notify.error('Splitting charts without splitting slices is not supported. Pretending that we are just splitting slices.');
toastNotifications.addDanger({
title: 'Splitting charts without splitting slices is not supported',
text: 'Pretending that we are just splitting slices.'
});
}
// start with splitting slices

View file

@ -18,9 +18,7 @@
*/
import chrome from '../chrome';
import { Notifier } from '../notify';
const notify = new Notifier({ location: 'Scripting Language Service' });
import { toastNotifications } from '../notify';
export function getSupportedScriptingLanguages() {
return ['painless'];
@ -35,7 +33,7 @@ export function GetEnabledScriptingLanguagesProvider($http) {
return $http.get(chrome.addBasePath('/api/kibana/scripts/languages'))
.then((res) => res.data)
.catch(() => {
notify.error('Error getting available scripting languages from Elasticsearch');
toastNotifications.addDanger('Error getting available scripting languages from Elasticsearch');
return [];
});
};

View file

@ -22,5 +22,6 @@ import { dirname } from 'path';
export const pkg = {
__filename: require.resolve('../../package.json'),
__dirname: dirname(require.resolve('../../package.json')),
...require('../../package.json')
// tslint:disable no-var-requires
...require('../../package.json'),
};

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const log = getService('log');
const PageObjects = getPageObjects(['common', 'home', 'settings']);
describe('test large number of fields @skipcloud', function () {
@ -34,6 +35,8 @@ export default function ({ getService, getPageObjects }) {
it('test_huge data should have expected number of fields', async function () {
const tabCount = await PageObjects.settings.getFieldsTabCount();
//default : maxPayloadBytes is 1048576
log.info('if there is a failure, start the server with "node scripts/functional_tests_server -- --server.maxPayloadBytes=1648576"');
expect(tabCount).to.be(EXPECTED_FIELD_COUNT);
});

View file

@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }) {
const renderable = getService('renderable');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('data table with index without time filter', function indexPatternCreation() {
describe.skip('data table with index without time filter', function indexPatternCreation() {
const vizName1 = 'Visualization DataTable without time filter';
before(async function () {

View file

@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
describe('vertical bar chart with index without time filter', function () {
describe.skip('vertical bar chart with index without time filter', function () {
const vizName1 = 'Visualization VerticalBarChart without time filter';
const initBarChart = async () => {

View file

@ -24,6 +24,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) {
const testSubjects = getService('testSubjects');
const flyout = getService('flyout');
const PageObjects = getPageObjects(['header', 'common']);
const find = getService('find');
return new class DashboardAddPanel {
async clickOpenAddPanel() {
@ -94,8 +95,10 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) {
}
async waitForEuiTableLoading() {
const addPanel = await testSubjects.find('dashboardAddPanel');
await addPanel.waitForDeletedByClassName('euiBasicTable-loading');
await retry.waitFor('dashboard add panel loading to complete', async () => {
const table = await find.byClassName('euiBasicTable');
return !((await table.getAttribute('class')).includes('loading'));
});
}
async closeAddPanel() {
@ -169,8 +172,6 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) {
async addVisualization(vizName) {
log.debug(`DashboardAddPanel.addVisualization(${vizName})`);
await this.ensureAddPanelIsShowing();
// workaround for timing issue with slideout animation
await PageObjects.common.sleep(500);
await this.filterEmbeddableNames(`"${vizName.replace('-', ' ')}"`);
await testSubjects.click(`addPanel${vizName.split(' ').join('-')}`);
await this.closeAddPanel();

View file

@ -72,6 +72,13 @@ export function FindProvider({ getService }) {
});
}
async byClassName(selector, timeout = defaultFindTimeout) {
log.debug(`findByCssSelector ${selector}`);
return await this._ensureElementWithTimeout(timeout, async remote => {
return await remote.findByClassName(selector);
});
}
async setValue(selector, text) {
return await retry.try(async () => {
const element = await this.byCssSelector(selector);

View file

@ -101,7 +101,7 @@ uiRoutes
return savedGraphWorkspaces.get($route.current.params.id)
.catch(
function () {
notify.error('Missing workspace');
toastNotifications.addDanger('Missing workspace');
}
);
@ -830,7 +830,7 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU
}
});
if(!savedObjectIndexPattern) {
notify.error('Missing index pattern:' + wsObj.indexPattern);
toastNotifications.addDanger(`'Missing index pattern ${wsObj.indexPattern}`);
return;
}

View file

@ -68,6 +68,24 @@ const HEADERS = {
};
export class IndexTableUi extends Component {
static getDerivedStateFromProps(props, state) {
// Deselct any indices which no longer exist, e.g. they've been deleted.
const { selectedIndicesMap } = state;
const indexNames = props.indices.map(index => index.name);
const selectedIndexNames = Object.keys(selectedIndicesMap);
const missingIndexNames = selectedIndexNames.filter(selectedIndexName => {
return !indexNames.includes(selectedIndexName);
});
if (missingIndexNames.length) {
const newMap = { ...selectedIndicesMap };
missingIndexNames.forEach(missingIndexName => delete newMap[missingIndexName]);
return { selectedIndicesMap: newMap };
}
return null;
}
constructor(props) {
super(props);
@ -82,6 +100,7 @@ export class IndexTableUi extends Component {
const newIsSortAscending = sortField === column ? !isSortAscending : true;
sortChanged(column, newIsSortAscending);
};
toggleAll = () => {
const allSelected = this.areAllItemsSelected();
if (allSelected) {
@ -96,6 +115,7 @@ export class IndexTableUi extends Component {
selectedIndicesMap
});
};
toggleItem = name => {
this.setState(({ selectedIndicesMap }) => {
const newMap = { ...selectedIndicesMap };
@ -109,6 +129,7 @@ export class IndexTableUi extends Component {
};
});
};
isItemSelected = name => {
return !!this.state.selectedIndicesMap[name];
};
@ -159,6 +180,7 @@ export class IndexTableUi extends Component {
}
return value;
}
buildRowCells(index) {
return Object.keys(HEADERS).map(fieldName => {
const { name } = index;
@ -174,6 +196,7 @@ export class IndexTableUi extends Component {
);
});
}
buildRows() {
const { indices = [], detailPanelIndexName } = this.props;
return indices.map(index => {
@ -221,7 +244,6 @@ export class IndexTableUi extends Component {
};
render() {
const {
filterChanged,
filter,
@ -336,4 +358,4 @@ export class IndexTableUi extends Component {
}
}
export const IndexTable = injectI18n(IndexTableUi);
export const IndexTable = injectI18n(IndexTableUi);

View file

@ -65,11 +65,7 @@ export function getSettingsCollector(server) {
// skip everything if defaultAdminEmail === undefined
if (defaultAdminEmail || (defaultAdminEmail === null && shouldUseNull)) {
kibanaSettingsData = {
xpack: {
default_admin_email: defaultAdminEmail
}
};
kibanaSettingsData = this.getEmailValueStructure(defaultAdminEmail);
this.log.debug(`[${defaultAdminEmail}] default admin email setting found, sending [${KIBANA_SETTINGS_TYPE}] monitoring document.`);
} else {
this.log.debug(`not sending [${KIBANA_SETTINGS_TYPE}] monitoring document because [${defaultAdminEmail}] is null or invalid.`);
@ -80,6 +76,13 @@ export function getSettingsCollector(server) {
// returns undefined if there was no result
return kibanaSettingsData;
},
getEmailValueStructure(email) {
return {
xpack: {
default_admin_email: email
}
};
}
});
}

View file

@ -26,7 +26,10 @@ export function settingsRoute(server, kbnServer) {
const { collectorSet } = server.usage;
const settingsCollector = collectorSet.getCollectorByType(KIBANA_SETTINGS_TYPE);
const settings = await settingsCollector.fetch(callCluster);
let settings = await settingsCollector.fetch(callCluster);
if (!settings) {
settings = settingsCollector.getEmailValueStructure(null);
}
const uuid = await getClusterUuid(callCluster);
const kibana = getKibanaInfoForStats(server, kbnServer);

View file

@ -35,6 +35,7 @@ export default function ({ getService }) {
expect(body.settings.kibana.transport_address.length > 0).to.eql(true);
expect(body.settings.kibana.version.length > 0).to.eql(true);
expect(body.settings.kibana.status.length > 0).to.eql(true);
expect(body.settings.xpack.default_admin_email).to.eql(null);
});
});
});