正在加载

Kibana 插件 API

技术预览

Kibana 平台插件是朝着稳定所有开发人员的 Kibana 架构迈出的重要一步。 我们确保插件可以继续使用他们现在使用的大多数相同技术,至少从技术角度来看是这样。

插件被定义为类,并通过一个简单的包装函数向 Kibana 呈现自己。 一个插件可以有浏览器端代码、服务器端代码或两者都有。 浏览器中的插件和服务器上的插件之间没有架构上的差异。 在这两个地方,您都以类似的方式描述您的插件,并且您以相同的方式与 Core 和其他插件进行交互。

具有客户端和服务器端代码的名为demo的 Kibana 插件的基本文件结构将是

plugins/
  demo
    kibana.json [1]
    public
      index.ts  [2]
      plugin.ts [3]
    server
      index.ts  [4]
      plugin.ts [5]

[1] kibana.json 是一个静态清单文件,用于标识插件并指定此插件是否具有服务器端代码、浏览器端代码或两者都有

{
  "id": "demo",
  "version": "kibana",
  "server": true,
  "ui": true
}

了解清单文件格式

注意

package.json 文件与 Kibana 发现和加载插件无关,并且会被 Kibana 忽略。

[2] public/index.ts 是此插件客户端代码的入口点。 它必须导出一个名为 plugin 的函数,该函数将接收一组标准的 core 功能作为参数。 它应该返回其插件类的实例以供 Kibana 加载。

import type { PluginInitializerContext } from '@kbn/core/server';
import { MyPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
  return new MyPlugin(initializerContext);
}

[3] public/plugin.ts 是客户端插件定义本身。 从技术上讲,它不需要是一个类,甚至不需要是与入口点不同的文件,但Elastic 的所有插件都应该以这种方式保持一致。 请参阅所有第一方 Elastic 插件的约定

import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from '@kbn/core/server';

export class MyPlugin implements Plugin {
  constructor(initializerContext: PluginInitializerContext) {}

  public setup(core: CoreSetup) {
    // called when plugin is setting up during Kibana's startup sequence
  }

  public start(core: CoreStart) {
    // called after all plugins are set up
  }

  public stop() {
    // called when plugin is torn down during Kibana's shutdown sequence
  }
}

[4] server/index.ts 是此插件服务器端代码的入口点。 它几乎在各个方面都相同于客户端入口点

import type { PluginInitializerContext } from '@kbn/core/server';

export async function plugin(initializerContext: PluginInitializerContext) {
  const { MyPlugin } = await import('./plugin');
  return new MyPlugin(initializerContext);
}

[5] server/plugin.ts 是服务器端插件定义。 此插件的形状与其客户端对应部分相同

import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from '@kbn/core/server';

export class MyPlugin implements Plugin {
  constructor(initializerContext: PluginInitializerContext) {}

  public setup(core: CoreSetup) {
    // called when plugin is setting up during Kibana's startup sequence
  }

  public start(core: CoreStart) {
    // called after all plugins are set up
  }

  public stop() {
    // called when plugin is torn down during Kibana's shutdown sequence
  }
}

Kibana 不对插件内部架构的方式施加任何技术限制,但与插件如何与核心 API 和其他插件公开的 API 集成相关的某些注意事项可能会极大地影响它们的构建方式。

构成 core 的各种独立域由一系列服务表示,并且许多这些服务公开了提供给所有插件的公共接口。 服务在其生命周期的不同部分公开不同的功能。 我们使用服务定义上专门命名的函数来描述核心服务和插件的生命周期。

Kibana 有三个生命周期:setupstartstop。 当 Kibana 在服务器上设置或在浏览器中加载时,每个插件的 setup 函数都会按顺序调用。 在完成所有插件的 setup 后,start 函数会按顺序调用。 当 Kibana 正常关闭服务器或关闭浏览器选项卡或窗口时,stop 函数会按顺序调用。

下表解释了每个生命周期与 Kibana 状态的关系。

生命周期 目的 服务器 浏览器
setup 执行“注册”工作以设置运行时环境 配置 REST API 端点,注册保存的对象类型等。 在 SPA 中配置应用程序路由,在扩展点中注册自定义 UI 元素等。
start 引导运行时逻辑 响应传入请求,请求 Elasticsearch 服务器等。 开始轮询 Kibana 服务器,更新 DOM 树以响应用户交互等。
stop 清理运行时 在服务器关闭之前处理活动句柄。 当用户离开 Kibana 时,将会话数据存储在 LocalStorage 中等。

相反,Kibana 平台插件中没有等效于 uiExports 的东西。 作为一般规则,通过 uiExports 注册的功能现在在 setup 阶段注册。 大部分其他内容应移动到 start 阶段。

核心服务公开的特定于生命周期的合约始终作为第一个参数传递给插件中等效的生命周期函数。 例如,核心 http 服务向所有插件 setup 函数公开了一个函数 createRouter。 要使用此函数注册 HTTP 路由处理程序,插件只需从第一个参数访问它

import type { CoreSetup } from '@kbn/core/server';

export class MyPlugin {
  public setup(core: CoreSetup) {
    const router = core.http.createRouter();
    // handler is called when '/path' resource is requested with `GET` method
    router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' }));
  }
}

不同的服务接口可以并且将会传递到 setupstartstop,因为某些功能在正在运行的插件的上下文中才有意义,而其他类型的功能可能存在限制,或者可能仅在停止的插件的上下文中才有意义。

例如,浏览器中的 stop 函数作为 window.onbeforeunload 事件的一部分被调用,这意味着您不能一定在这里可靠地执行异步代码。 因此,core 可能不会向浏览器中的插件 stop 函数提供任何异步函数。

所有插件的当前生命周期函数将在下一个生命周期开始之前执行。 也就是说,在执行任何 start 函数之前,会执行所有 setup 函数。

以下是核心服务为每个生命周期公开的合约

生命周期 服务器合约 浏览器合约
构造函数 PluginInitializerContext PluginInitializerContext
setup CoreSetup CoreSetup
start CoreStart CoreStart

插件可以公开公共接口以供其他插件使用。 与 core 类似,这些接口绑定到生命周期函数 setup 和/或 start

setupstart 返回的任何内容都将充当接口,虽然不是技术要求,但所有第一方 Elastic 插件也将公开该接口的类型。 强烈建议希望允许其他插件与其集成的第三方插件也公开其插件接口的类型。

foobar plugin.ts

import type { Plugin } from '@kbn/core/server';
export interface FoobarPluginSetup {
  getFoo(): string;
}

export interface FoobarPluginStart {
  getBar(): string;
}

export class MyPlugin implements Plugin<FoobarPluginSetup, FoobarPluginStart> {
  public setup(): FoobarPluginSetup {
    return {
      getFoo() {
        return 'foo';
      },
    };
  }

  public start(): FoobarPluginStart {
    return {
      getBar() {
        return 'bar';
      },
    };
  }
}
  1. 我们强烈建议插件作者为其插件显式声明公共接口。

与核心不同,插件公开的功能不会自动注入到所有插件中。 相反,如果一个插件希望使用另一个插件提供的公共接口,它必须首先在其kibana.json 清单文件中将该插件声明为依赖项。

demo kibana.json

{
  "id": "demo",
  "requiredPlugins": ["foobar"],
  "server": true,
  "ui": true
}

在插件清单中指定了这一点后,可以通过 setup 和/或 start 的第二个参数获得相应的接口

demo plugin.ts

import type { CoreSetup, CoreStart } from '@kbn/core/server';
import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';

interface DemoSetupPlugins {
  foobar: FoobarPluginSetup;
}

interface DemoStartPlugins {
  foobar: FoobarPluginStart;
}

export class AnotherPlugin {
  public setup(core: CoreSetup, plugins: DemoSetupPlugins) {
    const { foobar } = plugins;
    foobar.getFoo();
    // 'foo'
    foobar.getBar();
    // throws because getBar does not exist
  }

  public start(core: CoreStart, plugins: DemoStartPlugins) {
    const { foobar } = plugins;
    foobar.getFoo();
    // throws because getFoo does not exist
    foobar.getBar();
    // 'bar'
  }

  public stop() {}
}
  1. 必须手动组合插件依赖项的接口。 您可以通过从插件导入适当的类型并构造一个接口来实现这一点,其中属性名称是插件的 ID。
  2. 然后应使用这些手动构造的类型来指定插件第二个参数的类型。
  3. 请注意,setup 和 start 生命周期类型不同。 插件生命周期函数只能访问在生命周期期间公开的 API。
© . All rights reserved.