【译】Tim Rose 的kibana插件教程-自定义App插件

910 阅读9分钟

kibana官方没有插件的开发教程,Tim Rose的教程写的十分详尽,也是官方推荐的。由于这个系列的教程是英文版的,且基于kibana4,近日需要做kibana的开发,硬啃下这些教程之后,虽然这些教程比较古老,很多代码不能用了,但是开发思想还是通用的。记录下来,留下个爪。由于本人水平有限,错漏的地方欢迎大家指出。

原文链接:www.timroes.de/writing-kib…

原文标题Writing Kibana Plugins – Custom applications

你需要先读前面基础部分第一篇教程,才能开始本章节的学习。

本教程系列介绍了Kibana中如何创建一个自定义应用程序。一个插件可以包含一个应用程序,它是Kibana平台内部的一个独立的部分,可以放置任何你想展示的东西。Kibana只是给你提供了一个链接到这个部分,你可以随心所欲的设计这个插件。众所周知的例子就是Elastic 的timelion插件,他就是一个app插件。

在本教程中,我们将构建一个非常简单的Elasticsearch状态页面app。它列出所有索引,单击一个将带您到有关该索引的信息页面。您可以在下面的动画中看到这个效果。

在本教程中,我们将学习:

  • 创建app插件的基本结构
  • 插件如何和elasticsearch进行通信
  • 插件中如何创建多个子页面和页面之间的导航

完整插件的源代码可以在GitHub上找到。这个插件我用了很多ECMAScript 2015 语法。Kibana使用Webpack打包插件文件并编译成es5语法,所以您可以放心地使用ECMAScript 2015

你可以在GitHubGitHub中找到完整的代码

kibana5的全新架构

或许你已经注意到了,上面看到的kibana的页面你可能目前并不熟悉,这是kibana5的新的UI设计,到目前为止还没有发布,这个教程的代码时兼容kibana4的,对于kibana4和kibana5之间的,跟开发插件相关的差异,我会指出来的。(译者:这个已经。。。。)

创建插件基础结构

以前的教程中我们看到了index.js的结构。要注册一个新的插件,您可以使用uiExport对象的app属性,如下所示:

export default function (kibana) {
  return new kibana.Plugin({
   require: ['elasticsearch'],

    uiExports: {
      app: {
        title: 'Indices',
        description: 'An awesome Kibana plugin',
        main: 'plugins/elasticsearch_status/app',
        icon: 'plugins/elasticsearch_status/icon.svg'
      }
    }
});

在require中,我们可以引用其他的模块。常见的值是kibana和elasticsearch。指定这些模块因为kibana是在加载完这些模块后加载我们的插件,保证了引用的模块加载成功。我们在这里引用了elasticsearch模块,因为我们在后面要使用elasticsearch的数据。 在uiExports中的app属性,是一个对象,是来描述这个插件信息的。

  • title:插件的名字,会显示在kibana的侧边栏中(在kibana5之前是在logo下的应用程序菜单中)。
  • description:简单介绍插件信息,目前并没有在其他地方中使用。
  • icon:插件的图标,会被显示在kibana侧边栏的插件名字的旁边(kibana5之前是在应用程序菜单里),这个字段的值应该是一个字符串,功能如上所述。
  • main:插件的源码位置。默认plugins/插件名字/js文件名,指向插件的public文件夹下的js文件,在上述例子当中,我们的插件文件夹下应该有一个public/app.js文件,在package.json文件中,插件的id应该是elasticsearch_status

还有其他的一些参数,比如你如果不想让插件显示在导航中,可以添加参数hidden:true。(kibana 的状态页面就是这样。)

创建服务器API

如果要从插件中查询Elasticsearch,那么最好的解决方案就是,给Kibana服务器创建一个新的API。你插件中调用这个API,它会为你查询Elasticsearch。

为什么不直接从您的插件中查询Elasticsearch?当然,您也可以使用Elasticsearch JavaScript客户端直接从您的前端查询ES。但这些调用将在用户的浏览器中执行,从而导致CORS(交叉原始资源共享)问题。最好的解决方案是使用Kibana服务器。

因此,如上所述,我们的插件将会获取所有索引的列表,并且需要检索特定索引的状态。

我们来看看第二个接口。

要向Kibana添加新的服务器API,使用init方法可以指定:

// ...
return new kibana.Plugin({
  // ...
  init(server, options) {
    // Initialization goes here
  }
});

如果你不熟悉这个JavaScript语法,这只是一个快捷写法,你也可以写init: function(server, options) {…}。

传递给该方法的服务器对象实际上是一个hapiJS服务器对象。您可以按如下方式创建新接口:

// inside your init method:
server.route({
  path: '/api/elasticsearch_status/index/{name}',
  method: 'GET',
  handler(req, reply) {
    // more to come here in the next step
    reply("Hello World");
  }
});

这样,您可以在Kibana服务器上创建一个新的GET API。你现在可以调用该/api/elasticsearch_status/index/index名称接口(现在也没有做任何事情)。处理程序方法将获得两个参数:第一个是已经创建的请求。您可以从此处的请求中访问很多信息(例如,使用req.params.name,你将获取在URL中传递的索引的名称)。第二个参数是回复函数。您必须调用此函数并将其应该返回的数据传递给调用此API的客户端。

有关完整的文档,请查看官方hapi文档的路由方法。

查询elasticsearch

现在我们在处理函数中,需实际处理查询Elasticsearch以相关索引的数据。有一个实用方法来调用Elasticsearch,我们可以使用。这个方法也是我们为什么要在index.js中,要引入elasticsearch模块。以下代码应放在我们API的处理函数:

server.plugins.elasticsearch.callWithRequest(req, 'cluster.state', {
  metric: 'metadata',
  index: req.params.name
}).then(function (response) {
  reply(response.metadata.indices[req.params.name]);
});

我们需要将API中的请求,作为第一个参数传递给该callWithRequest方法。这种方法在Kibana服务器的调用和Elasticsearch的调用之间进行传递身份认证。第二个参数是我们要调用的Elasticsearch JavaScript客户端的函数的名称- 在我们的例子中,我们要调用该cluster.state()方法,并且我们要将索引的名称(从请求参数读出)传递给方法。

该方法返回一个Elasticsearch的响应的promise。在resolve函数中,我们将从响应中提取我们需要的数据(在我们的例子中是索引stats),并返回它(通过该reply方法)。

如果你的开发工作是基于Kibana 5.2以上的,callWithRequest的使用会有轻微的变化,这个博客中有概述。

我们创建了一个Kibana服务器API,现在可以调用它了。如果您注意GitHub的源代码,您将注意到,我将API生成提取到另一个模块,并且从init方法中调用此方法。我建议这样做,以保持你的代码可读性 - 如果你有很多API你创建,你甚至可能想要使用多个模块。

第二个服务器API(用于获取所有索引的列表)可以在源代码中找到。我不会在这篇博文中详细介绍,因为我们已经涵盖了所有主题,你可以自己写。

创建前端

我们应该为我们的插件创建一个前端。我们之前已经在index.js中注册了一个app.js作为主文件。现在是来写他们的时候了。

我们将插入文件的前两行如下:

import 'ui/autoload/styles';
import './less/main.less';

如果您使用 Kibana 5,则第一行很重要,您应该始终将其放在插件中。这将使插件加载Kibana常用的所有样式。如果您不导入此模块,您的应用程序会和kibana框架看起来格格不入。如果您使用Kibana 4,此文件不存在,您无法导入(这让我们回到第一篇文章中的巨大警告,关于缺少稳定的公共API)。

第二行是可选的,是app插入自己的LESS样式的例子。您只需导入您的LESS文件。您也可以使用SASS。我建议使用相对路径,因为这些文件也在您的public件夹,所以你每次都在路径上写上你的插件ID。

创建路由选项

Kibana使用AngularJS的ngRouter在页面之间进行路由。如果您的app想要使用路由,则必须明确启用它,并在app.js文件中配置一些路由:

import uiRoutes from 'ui/routes';

import overviewTemplate from './templates/index.html';
import detailTemplate from './templates/detail.html';

uiRoutes.enable();
uiRoutes
.when('/', {
  template: overviewTemplate,
  controller: 'elasticsearchStatusController',
  controllerAs: 'ctrl'
})
.when('/index/:name', {
  template: detailTemplate,
  controller: 'elasticsearchDetailController',
  controllerAs: 'ctrl'
});

如果需要使用路由,必须调用uiRoutes.enable()。之后,您可以使用when和otherwise调用,就像您使用$routeProvider。在这种情况下,我们要配置两个路由:一个用于基本路径,一个用于路径/index/:name,name是索引名称的占位符。可以通过使用上面的import语句,将实际的html文件(这文件放在templates文件夹中)引入,并设置两个路由的模板。我们还使用两个控制器,我们还没有写。

下面我们写控制器,我们使用Kibana的全局模块注册模块:

import uiModules from 'ui/modules';

uiModules
.get('app/elasticsearch_status')
.controller('elasticsearchStatusController', function ($http) {
  $http.get('../api/elasticsearch_status/indices').then((response) => {
    this.indices = response.data;
  });
});

uiModules是Kibana的核心服务,负责处理应用中的所有模块。如果要获取或创建一个,请使用其get方法。第一个参数是要获取或创建的模块的名称。如果模块已经存在则把存在的模块返回,如果没有创建它,返回该模块。第二个参数可以是一个模块数组,放置模块的依赖。如果模块已经存在,那么这些模块在返回之前,将被添加到模块的依赖关系列表中。这些模块不存在,只需将它们添加到新创建的模块中即可。

此行为与angular.module方法不同,你创建模块,并指定依赖关系和获取依赖时,和不传递第二个参数时,都能发现。使用这个服务,Kibana也会负责处理加载我们的Angular模块。

上面的控制器使用$http服务,来从我们的接口获取索引列表,并将其存储在控制器中。

最后一个需要补充的部分现在,是我们的模板templates/index.html:

<div class="container">
  <div class="row">
    <div class="col-12-sm">
      <h1>Elasticsearch Status</h1>
      <ul class="indexList">
        <li ng-repeat="index in ctrl.indices">
          <a href="#/index/{{index}}">{{ index }}</a>
        </li>
      </ul>
    </div>
  </div>
</div>

我们的教程中的HTML保持相当简单。我们只是ng-repeat用来迭代,从API中检索所有的索引,并链接到它们。此外,我们使用一些Bootstrap的CSS类来设计我们的内容。详细页面的HTML可以在GitHub的源代码中找到。

在Kibana 5开始,Kibana将只提供给你侧边的导航栏链接,如上面的动图所示。一旦你被切换到你的插件,kibana没有提供标题栏或其他的东西。如果你想要其他样式布局,你将要自己在app中去创建它。在Kibana5之前,您仍然可以获得一些bar,并可以通过服务进行修改:

import chrome from 'ui/chrome';

chrome
.setNavBackground('#FF00FF') // Turns the navbar in beautiful pink
.setBrand({
  logo: '<CSS background property value for the logo>',
  smallLogo: '<CSS background property value a smaller version>'
});

还有其他的一些方法来创建tabs,等。但是当你创建一个app的时候,要记住这些东西在kibana5的时候会被干掉。那么你的app可能就不可用了,因为你所依赖的tabs导航已经不可用了。

下一步

从这个非常简单的app中,你已经学到了很多有用的api了,足够去创建一个非常棒的自定义app了,所以下一个timelion app就由你来创建啦。