diff --git a/src/plugins/status_page/public/status_page.html b/src/plugins/status_page/public/status_page.html index 09ef885ac32c4..29c83c37eb836 100644 --- a/src/plugins/status_page/public/status_page.html +++ b/src/plugins/status_page/public/status_page.html @@ -15,27 +15,48 @@

-
-

Installed Plugins

+
+

Status Breakdown

+

- No plugin status information available + No status information available

- +
+ + + + + + + + + +
ServiceStatus
{{status.id}} + + {{status.message}} +
+ + - + - - - + + + + diff --git a/src/plugins/status_page/public/status_page.js b/src/plugins/status_page/public/status_page.js index 2fc65f299b803..b623340103767 100644 --- a/src/plugins/status_page/public/status_page.js +++ b/src/plugins/status_page/public/status_page.js @@ -28,9 +28,11 @@ const chrome = require('ui/chrome') const data = resp.data; ui.metrics = data.metrics; - ui.statuses = data.status.statuses; ui.name = data.name; + ui.statuses = _.groupBy(data.status.statuses, s => s.plugin ? 'plugins' : 'services'); + if (!_.size(ui.statuses)) ui.statuses = null; + const overall = data.status.overall; if (!ui.serverState || (ui.serverState !== overall.state)) { ui.serverState = overall.state; diff --git a/src/plugins/status_page/public/status_page.less b/src/plugins/status_page/public/status_page.less index c18e5a3ad8c32..c63a323b4c265 100644 --- a/src/plugins/status_page/public/status_page.less +++ b/src/plugins/status_page/public/status_page.less @@ -5,9 +5,9 @@ @status-metric-border: #aaa; @status-metric-title-color: #666; -@status-plugins-bg: #fff; -@status-plugins-border: #bbb; -@status-plugins-headings-color: #666; +@status-statuses-bg: #fff; +@status-statuses-border: #bbb; +@status-statuses-headings-color: #666; @status-default: #7c7c7c; @status-green: #94c63d; @@ -58,13 +58,13 @@ } } -// plugin status table section -.plugin_status_wrapper { +// status status table section +.status_status_wrapper { margin-top: 25px; margin-left: -5px; margin-right: -5px; border-top:2px solid; - background-color: @status-plugins-bg; + background-color: @status-statuses-bg; padding: 10px; h3 { @@ -78,7 +78,7 @@ text-align: center; } - .plugin_status_breakdown { + .status_status_breakdown { margin-left: 0; margin-right: 0; @@ -86,16 +86,16 @@ height:30px; line-height:30px; border-bottom:1px solid; - border-bottom-color: @status-plugins-border; + border-bottom-color: @status-statuses-border; } th { - color:@status-plugins-headings-color; + color:@status-statuses-headings-color; font-weight: normal; height:25px; line-height:25px; border-bottom:1px solid; - border-bottom-color: @status-plugins-border; + border-bottom-color: @status-statuses-border; } .status_name { @@ -111,13 +111,13 @@ } } -//plugin state -.plugin_state(@color, @icon) { - .plugin_state_color { +//status state +.status_state(@color, @icon) { + .status_state_color { color: @color; } - .plugin_state_icon:before { + .status_state_icon:before { content: @icon; } @@ -130,20 +130,20 @@ } } -.plugin_state_default { - .plugin_state(@status-default, @icon-default); +.status_state_default { + .status_state(@status-default, @icon-default); } -.plugin_state_green { - .plugin_state(@status-green, @icon-green); +.status_state_green { + .status_state(@status-green, @icon-green); } -.plugin_state_yellow { - .plugin_state(@status-yellow, @icon-yellow); +.status_state_yellow { + .status_state(@status-yellow, @icon-yellow); } -.plugin_state_red { - .plugin_state(@status-red, @icon-red); +.status_state_red { + .status_state(@status-red, @icon-red); } //server state @@ -156,7 +156,7 @@ content: @icon; } - .plugin_status_wrapper { + .status_status_wrapper { border-top-color: @color; } } diff --git a/src/server/plugins/plugin.js b/src/server/plugins/plugin.js index 9b805f0c411b4..877ff2d887bb8 100644 --- a/src/server/plugins/plugin.js +++ b/src/server/plugins/plugin.js @@ -127,7 +127,7 @@ module.exports = class Plugin { server.exposeStaticDir(`/plugins/${id}/{path*}`, this.publicDir); } - this.status = kbnServer.status.create(this); + this.status = kbnServer.status.createForPlugin(this); server.expose('status', this.status); return await attempt(this.externalInit, [server, options], this); diff --git a/src/server/status/__tests__/server_status.js b/src/server/status/__tests__/server_status.js index 6c3b627c6981a..3d08250fbf81f 100644 --- a/src/server/status/__tests__/server_status.js +++ b/src/server/status/__tests__/server_status.js @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { find } from 'lodash'; import expect from 'expect.js'; import sinon from 'sinon'; @@ -17,31 +17,63 @@ describe('ServerStatus class', function () { serverStatus = new ServerStatus(server); }); - describe('#create(plugin)', function () { + describe('#create(id)', () => { + it('should create a new plugin with an id', () => { + const status = serverStatus.create('someid'); + expect(status).to.be.a(Status); + }); + }); + + describe('#createForPlugin(plugin)', function () { it('should create a new status by plugin', function () { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); expect(status).to.be.a(Status); }); }); - describe('#get(name)', function () { - it('exposes plugins by its id/name', function () { - let status = serverStatus.create(plugin); - expect(serverStatus.get('name')).to.be(status); + describe('#get(id)', () => { + it('exposes statuses by their id', () => { + const status = serverStatus.create('statusid'); + expect(serverStatus.get('statusid')).to.be(status); + }); + + it('does not get the status for a plugin', () => { + serverStatus.createForPlugin(plugin); + expect(serverStatus.get(plugin)).to.be(undefined); + }); + }); + + describe('#getForPluginId(plugin)', function () { + it('exposes plugin status for the plugin', function () { + let status = serverStatus.createForPlugin(plugin); + expect(serverStatus.getForPluginId(plugin.id)).to.be(status); + }); + + it('does not get plain statuses by their id', function () { + serverStatus.create('someid'); + expect(serverStatus.getForPluginId('someid')).to.be(undefined); + }); + }); + + describe('#getState(id)', function () { + it('should expose the state of a status by id', function () { + let status = serverStatus.create('someid'); + status.green(); + expect(serverStatus.getState('someid')).to.be('green'); }); }); - describe('#getState(name)', function () { - it('should expose the state of the plugin by name', function () { - let status = serverStatus.create(plugin); + describe('#getStateForPluginId(plugin)', function () { + it('should expose the state of a plugin by id', function () { + let status = serverStatus.createForPlugin(plugin); status.green(); - expect(serverStatus.getState('name')).to.be('green'); + expect(serverStatus.getStateForPluginId(plugin.id)).to.be('green'); }); }); describe('#overall()', function () { it('considers each status to produce a summary', function () { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); expect(serverStatus.overall().state).to.be('uninitialized'); @@ -69,25 +101,24 @@ describe('ServerStatus class', function () { it('serializes to overall status and individuals', function () { const pluginOne = {id: 'one', version: '1.0.0'}; const pluginTwo = {id: 'two', version: '2.0.0'}; - const pluginThree = {id: 'three', version: '3.0.0'}; - let one = serverStatus.create(pluginOne); - let two = serverStatus.create(pluginTwo); - let three = serverStatus.create(pluginThree); + let service = serverStatus.create('some service'); + let p1 = serverStatus.createForPlugin(pluginOne); + let p2 = serverStatus.createForPlugin(pluginTwo); - one.green(); - two.yellow(); - three.red(); + service.green(); + p1.yellow(); + p2.red(); - let obj = JSON.parse(JSON.stringify(serverStatus)); - expect(obj).to.have.property('overall'); - expect(obj.overall.state).to.eql(serverStatus.overall().state); - expect(obj.statuses).to.have.length(3); + let json = JSON.parse(JSON.stringify(serverStatus)); + expect(json).to.have.property('overall'); + expect(json.overall.state).to.eql(serverStatus.overall().state); + expect(json.statuses).to.have.length(3); - let outs = _.indexBy(obj.statuses, 'name'); - expect(outs.one).to.have.property('state', 'green'); - expect(outs.two).to.have.property('state', 'yellow'); - expect(outs.three).to.have.property('state', 'red'); + const out = status => find(json.statuses, { id: status.id }); + expect(out(service)).to.have.property('state', 'green'); + expect(out(p1)).to.have.property('state', 'yellow'); + expect(out(p2)).to.have.property('state', 'red'); }); }); diff --git a/src/server/status/__tests__/status.js b/src/server/status/__tests__/status.js index eafeb9f2f7fc6..703c7628da4a6 100644 --- a/src/server/status/__tests__/status.js +++ b/src/server/status/__tests__/status.js @@ -15,11 +15,11 @@ describe('Status class', function () { }); it('should have an "uninitialized" state initially', function () { - expect(serverStatus.create(plugin)).to.have.property('state', 'uninitialized'); + expect(serverStatus.createForPlugin(plugin)).to.have.property('state', 'uninitialized'); }); it('emits change when the status is set', function (done) { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); status.once('change', function (prev, prevMsg) { expect(status.state).to.be('green'); @@ -42,7 +42,7 @@ describe('Status class', function () { }); it('should only trigger the change listener when something changes', function () { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); let stub = sinon.stub(); status.on('change', stub); status.green('Ready'); @@ -52,18 +52,18 @@ describe('Status class', function () { }); it('should create a JSON representation of the status', function () { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); status.green('Ready'); let json = status.toJSON(); - expect(json.name).to.eql(plugin.id); - expect(json.version).to.eql(plugin.version); + expect(json.id).to.eql(status.id); + expect(json.plugin).to.eql({ id: plugin.id, version: plugin.version }); expect(json.state).to.eql('green'); expect(json.message).to.eql('Ready'); }); it('should call on handler if status is already matched', function (done) { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); let msg = 'Test Ready'; status.green(msg); @@ -77,7 +77,7 @@ describe('Status class', function () { }); it('should call once handler if status is already matched', function (done) { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); let msg = 'Test Ready'; status.green(msg); @@ -92,7 +92,7 @@ describe('Status class', function () { function testState(color) { it(`should change the state to ${color} when #${color}() is called`, function () { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); let message = 'testing ' + color; status[color](message); expect(status).to.have.property('state', color); @@ -100,7 +100,7 @@ describe('Status class', function () { }); it(`should trigger the "change" listner when #${color}() is called`, function (done) { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); let message = 'testing ' + color; status.on('change', function (prev, prevMsg) { expect(status.state).to.be(color); @@ -114,7 +114,7 @@ describe('Status class', function () { }); it(`should trigger the "${color}" listner when #${color}() is called`, function (done) { - let status = serverStatus.create(plugin); + let status = serverStatus.createForPlugin(plugin); let message = 'testing ' + color; status.on(color, function (prev, prevMsg) { expect(status.state).to.be(color); diff --git a/src/server/status/plugin_status.js b/src/server/status/plugin_status.js new file mode 100644 index 0000000000000..4f7a60e433f58 --- /dev/null +++ b/src/server/status/plugin_status.js @@ -0,0 +1,22 @@ +import _ from 'lodash'; +import states from './states'; +import Status from './status'; + +class PluginStatus extends Status { + constructor(plugin, server) { + super(`plugin:${plugin.id}@${plugin.version}`, server); + this.plugin = plugin; + } + + toJSON() { + return { + ...super.toJSON(), + plugin: { + id: this.plugin.id, + version: this.plugin.version + } + }; + } +} + +module.exports = PluginStatus; diff --git a/src/server/status/server_status.js b/src/server/status/server_status.js index 99cc6e69fb7a0..5ec535b668b45 100644 --- a/src/server/status/server_status.js +++ b/src/server/status/server_status.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import states from './states'; import Status from './status'; +import PluginStatus from './plugin_status'; module.exports = class ServerStatus { constructor(server) { @@ -9,8 +10,16 @@ module.exports = class ServerStatus { this._created = {}; } - create(plugin) { - return (this._created[plugin.id] = new Status(plugin, this.server)); + create(id) { + const status = new Status(id, this.server); + this._created[status.id] = status; + return status; + } + + createForPlugin(plugin) { + const status = new PluginStatus(plugin, this.server); + this._created[status.id] = status; + return status; } each(fn) { @@ -22,12 +31,26 @@ module.exports = class ServerStatus { }); } - get(name) { - return this._created[name]; + get(id) { + return this._created[id]; + } + + getForPluginId(pluginId) { + return _.find(this._created, s => + s.plugin && s.plugin.id === pluginId + ); + } + + getState(id) { + const status = this.get(id); + if (!status) return undefined; + return status.state || 'uninitialized'; } - getState(name) { - return _.get(this._created, [name, 'state'], 'uninitialized'); + getStateForPluginId(pluginId) { + const status = this.getForPluginId(pluginId); + if (!status) return undefined; + return status.state || 'uninitialized'; } overall() { diff --git a/src/server/status/status.js b/src/server/status/status.js index e19278e6e7884..2aefc1fff1d8f 100644 --- a/src/server/status/status.js +++ b/src/server/status/status.js @@ -3,18 +3,26 @@ import states from './states'; import { EventEmitter } from 'events'; class Status extends EventEmitter { - constructor(plugin, server) { + constructor(id, server) { super(); - this.plugin = plugin; + if (!id || typeof id !== 'string') { + throw new TypeError('Status constructor requires an `id` string'); + } + + this.id = id; this.since = new Date(); this.state = 'uninitialized'; this.message = 'uninitialized'; this.on('change', function (previous, previousMsg) { this.since = new Date(); - let tags = ['status', `plugin:${this.plugin.toString()}`]; - tags.push(this.state === 'red' ? 'error' : 'info'); + + const tags = [ + 'status', + this.id, + this.state === 'red' ? 'error' : 'info' + ]; server.log(tags, { tmpl: 'Status changed from <%= prevState %> to <%= state %><%= message ? " - " + message : "" %>', @@ -28,8 +36,7 @@ class Status extends EventEmitter { toJSON() { return { - name: this.plugin.id, - version: this.plugin.version, + id: this.id, state: this.state, icon: states.get(this.state).icon, message: this.message,
NamePlugin Version Status
{{status.name}}{{status.version}}
{{status.plugin.id}}{{status.plugin.version}} - + {{status.message}}