はじめに
「Kibana 4のプラグインをつくってみよう」という記事を書いてから1年が経過しました。
この記事を書いた時点ではKibanaのプラグインシステムは公式にサポートされたものではありませんでしたが、先日Kibana 4.2がリリースされ、ドキュメントにプラグインについて記述されていました。
これでようやく公式にプラグインがつくれるぜ!と思ったわけですが、よく調べてみるとなにやら違和感があります。
どうやらこれは、Kibanaにちょっとした機能を追加するようなプラグインではなくて、Kibanaプラットフォーム上に新たなアプリケーションをつくるための仕組みのようです。
バックエンドにNode.jsとElasticsearch、フロントエンドにAngularJSをつかったアプリケーションが、簡単につくれるようになっています。
思っていたものとはちょっと違ったのですが、せっかくなのでプラグインのつくり方を解説したいと思います。
ちなみに、現在公開されているKibanaプラグインには以下のようなものがあります。
Timelionは昨日@johtaniさんが「Timelionの紹介 - Elasticsearch Advent Calendar 2015 1日目」で紹介されていますね。
プラグインの実装準備
それではさっそくプラグインをつくっていきましょう。
なお、Node.js、Kibana 4.2以降、Elasticsearch 2.0以降はすでインストールされているものとします。
プラグインのひな形をつくる
まずはプラグインのひな形をつくりましょう。
今回は Kibana Plugin Yeoman Generator を利用します。
次のコマンドでYeomanとKibana Plugin Yeoman Generatorをインストールします。
npm install -g yo
# 本記事執筆時点では、npmに上がっているgenerator-kibana-pluginが古かったので、githubからインストールします
npm install -g https://github.com/elastic/generator-kibana-plugin.git
次のコマンドでソースコードのひな形を生成します。
mkdir my-kibana-plugin
cd my-kibana-plugin
yo kibana-plugin
プラグイン名と説明を入力すると、以下のようなファイルが生成されます。
ここではmy-kibana-plugin
という名前にしています。
my-kibana-plugin
├── gulpfile.js
├── index.js
├── package.json
├── public
│ ├── app.js
│ ├── less
│ │ └── main.less
│ └── templates
│ └── index.html
└── server
└── routes
└── example.js
クライアントサイドのコードがpublic
の下に、サーバーサイドのコードがserver
の下にあります。
プラグインのビルド
生成されたコードをビルドしてパッケージをつくります。
npm install
npm run package
Kibanaのインストールディレクトリの下で次のコマンドを実行すると、プラグインをインストールすることができます。-i
オプションでプラグイン名、-u
オプションでビルドしたパッケージのパスを指定してください。
./bin/kibana plugin -i my-kibana-plugin -u file:///path/to/my-kibana-plugin/target/my-kibana-plugin-0.1.0.tar.gz
Kibanaを起動します。
./bin/kibana
ブラウザからKibanaの画面を開くと、ナビゲーションバーの一番右に見慣れぬアイコンが表示されているので、これをクリックします。
プラグインのアイコンをクリックして以下のような画面が表示されれば成功です。
開発モード
プラグインの開発時に、ソースコードを少し修正するたびにビルドして、Kibanaを再起動するのはとても面倒です。
そういうときはKibanaの開発モードを利用しましょう。
Kibanaの開発モードは、公式サイトからダウンロードできるものでは利用することができず、ソースコードからKibanaをビルドする必要があります。
CONTRIBUTING.mdを参考にしてKibanaをセットアップしてください。なお、ビルドのスピード向上のため、kibana.dev.yml
を設定しておくことをおすすめします。
Kibanaを開発モードで利用するには、以下のように--dev
オプションを指定します。
./bin/kibana --dev
つづいて、Kibanaのインストールディレクトリと同じ階層にプラグインのディレクトリを置きます。
├── kibana
└── my-kibana-plugin
プラグインのディレクトリに移動して、ビルドをおこないます。
cd my-kibana-plugin
npm start
これで、プラグインのソースコードを変更すると、自動的にkibana/installedPlugins/
にプラグインが配備され、さらにKibanaの再起動がおこなわれます。
プラグインの実装
最近、スコットランド出身の同僚が「ぼくはゲームで日本語おぼえたよ。英語もゲームやってればすぐおぼえるでしょ。」というので、英語のゲームにチャレンジしていたのですが、英単語を調べるのにすごく時間がかかってしまいまともにゲームが進められません。
というわけで、ゲーム内の英語テキストと日本語化MODのテキストをElasticsearchに突っ込んでおいて、英語のテキストを入力したら対応する日本語のテキストを表示するだけのシンプルなアプリをつくってみました。
これを題材にして、Kibanaプラグインのつくり方を解説していきます。
index.js
まずはindex.js
を見てみましょう。
module.exports = function (kibana) {
return new kibana.Plugin({
name: 'elastic-translator',
require: ['kibana', 'elasticsearch'],
uiExports: {
app: {
title: 'Elastic Translator',
description: 'Sample for Kibana plugin',
main: 'plugins/elastic-translator/app',
injectVars: function (server, options) {
var config = server.config();
return {
kbnIndex: config.get('kibana.index'),
esApiVersion: config.get('elasticsearch.apiVersion'),
esShardTimeout: config.get('elasticsearch.shardTimeout')
};
}
}
},
init: function (server, options) {
var searchApi = require('./server/api/search');
searchApi(server);
}
});
};
このコードでは、プラグインの各種設定を記述します。
Kibana.Plugin
のコンストラクタに渡しているuiExports.app.main
には、クライアントサイドのコードのパスを指定します。
init
メソッドではサーバーサイドのコードの初期化をおこないます。
サーバーサイドコード
つづいて、サーバーサイドのコードは以下のようになります。
module.exports = function (server) {
var client = server.plugins.elasticsearch.client;
server.route({
path: '/elastic-translator/api/search',
method: 'GET',
handler: function (req, reply) {
client.search({
index: 'elastic-translator',
q: 'default_text_en:"' + req.query.keywords + '"'
}, function (error, response) {
if (!error) {
reply(response.hits.hits);
} else {
server.log(['error', 'elastic-translator'], error);
reply(error);
}
});
}
});
};
API
Kibanaでは、Webアプリケーションフレームワークとして hapi.js をつかっています。
上記のコードの中でserver.route
がhapi.js
のAPIになります。
ここでは、http://localhost:5601/elastic-translator/api/search
にGET
リクエストを投げたときのAPIを定義しています。
詳しくは以下のドキュメントを参考にしてください。
Elasticsearch
server.plugins.elasticsearch.client
というオブジェクトをつかうと、Kibanaで利用しているElasticsearchにアクセスすることができます。
上記のコードではclient.search
メソッドを利用して、elastic-translator
というインデックスのdefault_text_en
フィールドにkeywordsで指定した文字列が含まれるドキュメントを検索して返しています。
Elasticsearch Clientの詳細については、以下のドキュメントを参考にしてください。
ログを出力する
ログを出力するには、以下のようにserver.log
メソッドにログレベル、プラグイン名、ログメッセージを渡します。
server.log(['info', 'my-kibana-plugin'], 'log message');
Kibanaのログに以下のように出力されます。
server log [21:46:31.321] [info][my-kibana-plugin] log message
クライアントサイドコード
最後にクライアントサイドのコードを見てみましょう。
require('plugins/elastic-translator/less/main.less');
require('ui/chrome').setNavBackground('#222222').setTabs([
{id: 'search', title: 'Search'},
{id: 'setting', title: 'Setting'}
]);
var app = require('ui/modules').get('app/elastic-translator', []);
require('ui/routes')
.when('/search', {
template: require('plugins/elastic-translator/templates/search.html')
})
.when('/setting', {
template: require('plugins/elastic-translator/templates/setting.html')
})
.otherwise({
redirectTo: '/search'
});
app.controller('searchController', function ($scope, $http) {
$scope.search = function (keywords) {
$http.get('/elastic-translator/api/search', {params: {keywords: keywords}})
.then(function (response) {
$scope.result = response;
});
};
});
ナビゲーションバーの設定
以下のコードでは、ナビゲーションバーの設定をおこなっています。
ナビゲーションバーの背景色の指定、アイコンの指定、タブの定義やデフォルトタブの指定などをおこなうことができます。
ここでは、Search
とSetting
の2つのタブを用意しています。
require('ui/chrome').setNavBackground('#222222').setTabs([
{id: 'search', title: 'Search'},
{id: 'setting', title: 'Setting'}
]);
ルーティング
クライアントサイドのルーティングには ngRoute を使っています。
以下のコードでは、search
とsetting
にアクセスしたときのルーティングを指定しています。
require('ui/routes')
.when('/search', {
template: require('plugins/elastic-translator/templates/search.html')
})
.when('/setting', {
template: require('plugins/elastic-translator/templates/setting.html')
})
.otherwise({
redirectTo: '/search'
});
APIの呼び出し
最後に、先ほどサーバーサイドのコードで定義したAPIを呼び出します。
まず、以下のようなテンプレートを用意します。
検索キーワードを入力するためのinput
タグと、検索結果を羅列しているだけのシンプルなものです。
詳細についてはAngularJSのドキュメントを参照してください。
<div class="container" ng-controller="searchController">
<form role="form" class="search-form" ng-submit="search(keywords)" name="searchKeywords">
<div class="input-group">
<input ng-model="keywords" placeholder="Keywords ..." type="text" class="form-control">
<span class="input-group-btn">
<button type="submit" >
<span class="fa fa-play"></span></button>
</span>
</div>
</form>
<div class="row">
<div class="col-12-sm">
<div ng-repeat="val in result.data">
<div class="panel panel-default">
<div class="panel-heading">
{{val._source.default_text_en}}
</div>
<div class="panel-body">
{{val._source.default_text_ja}}
</div>
</div>
</div>
</div>
</div>
</div>
以下のコードでは、AngularJSの controller を定義して、その中で $http をつかってサーバーにリクエストを投げています。
app.controller('searchController', function ($scope, $http) {
$scope.search = function (keywords) {
$http.get('/elastic-translator/api/search', {params: {keywords: keywords}})
.then(function (response) {
$scope.result = response;
});
};
});
まとめ
後半はちょっと駆け足気味になってしまいましたが、いかがだったでしょうか。
個人的な感想としては、Elasticsearchを使ったWebアプリケーションがさくっとお手軽につくれるのがいいなと思いました。
また、今回は紹介しきれませんでしたが、アプリケーション内でKibanaが提供しているコンポーネントを利用することもできるようです。
グラフのコンポーネントを使いまわすことができれば、いろいろと面白いアプリケーションがつくれそうで期待が膨らみますね!