├── .eslintrc
├── .gitignore
├── demo.gif
├── server
├── routes
│ ├── index.js
│ └── notification
│ │ ├── index.js
│ │ ├── push.js
│ │ └── pull.js
├── lib
│ ├── constants.js
│ ├── notification_mappings.js
│ ├── replace_injected_vars.js
│ ├── parse_index_pattern.js
│ └── register_template.js
└── init.js
├── public
├── images
│ └── ic_message_white_18px.svg
├── nav_control
│ ├── nav_control.html
│ └── nav_control.js
└── components
│ └── notification_center
│ ├── lib
│ ├── get_notification_classes.js
│ ├── stored_config.js
│ ├── polling_notifications.js
│ └── stored_notifications.js
│ ├── index.less
│ ├── index.js
│ └── template.html
├── package.json
├── LICENSE
├── index.js
├── TRANSLATION.md
└── README.md
/.eslintrc:
--------------------------------------------------------------------------------
1 | ---
2 | extends: "@elastic/kibana"
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log*
2 | node_modules
3 | /build/
4 | .*.md
5 | .vscode
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sw-jung/kibana_notification_center/HEAD/demo.gif
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | import { notification } from './notification';
2 |
3 | export function routes(server) {
4 | notification(server);
5 | };
6 |
--------------------------------------------------------------------------------
/server/lib/constants.js:
--------------------------------------------------------------------------------
1 | export const constants = {
2 | API_BASE_URL: '/api/notification_center',
3 |
4 | RESPONSE: {
5 | OK: {
6 | acknowledged: true
7 | }
8 | }
9 | };
--------------------------------------------------------------------------------
/server/routes/notification/index.js:
--------------------------------------------------------------------------------
1 | import { push } from './push';
2 | import { pull } from './pull';
3 |
4 | export function notification(server) {
5 | push(server);
6 | pull(server);
7 | };
8 |
--------------------------------------------------------------------------------
/server/lib/notification_mappings.js:
--------------------------------------------------------------------------------
1 | export const mappings = {
2 | notification: {
3 | properties: {
4 | timestamp: {
5 | type: 'date'
6 | },
7 | content: {
8 | type: 'string'
9 | }
10 | }
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/public/images/ic_message_white_18px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/server/init.js:
--------------------------------------------------------------------------------
1 | import { constants } from './lib/constants';
2 | import { registerTemplate } from './lib/register_template';
3 | import { routes } from './routes';
4 |
5 | export function init(server) {
6 | if (!!server.config().get('notification_center.api.enabled')) {
7 | registerTemplate(server);
8 | routes(server);
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/server/lib/replace_injected_vars.js:
--------------------------------------------------------------------------------
1 | import { extend } from 'lodash';
2 |
3 | export async function replaceInjectedVars(originalInjectedVars, request, server) {
4 | const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
5 | return callWithRequest(request, 'info')
6 | .then(resp => extend(originalInjectedVars, {
7 | clusterUuid: resp.cluster_uuid
8 | }));
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/public/nav_control/nav_control.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/public/components/notification_center/lib/get_notification_classes.js:
--------------------------------------------------------------------------------
1 | export function getNotificationClasses(notif = {}) {
2 | const color = ((type) => {
3 | switch (type) {
4 | case 'banner':
5 | return 'success';
6 | case 'danger':
7 | return 'error';
8 | case 'warning':
9 | return 'warning';
10 | default:
11 | return 'info';
12 | };
13 | })(notif.type);
14 |
15 | const icon = notif.icon || 'info-circle';
16 | return `kuiIcon--${color} fa-${icon}`;
17 | };
--------------------------------------------------------------------------------
/server/lib/parse_index_pattern.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import { find } from 'lodash';
3 |
4 | const dateExprPattern = /%{\+([^}]+)}/g;
5 |
6 | export function parseWithWildcard(index) {
7 | return parseWithReplacer(index, '*');
8 | };
9 |
10 | export function parseWithTimestamp(index, timestamp = moment()) {
11 | return parseWithReplacer(index, (match, format) => timestamp.format(format));
12 | };
13 |
14 | export function parseWithReplacer(index, replacer) {
15 | return index.replace(dateExprPattern, replacer);
16 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notification_center",
3 | "version": "6.2.4",
4 | "description": "Notification center for kibana",
5 | "main": "index.js",
6 | "kibana": {
7 | "version": "6.2.4"
8 | },
9 | "scripts": {
10 | "lint": "eslint",
11 | "start": "plugin-helpers start",
12 | "test:server": "plugin-helpers test:server",
13 | "test:browser": "plugin-helpers test:browser",
14 | "build": "plugin-helpers build",
15 | "postinstall": "plugin-helpers postinstall"
16 | },
17 | "devDependencies": {
18 | "@elastic/eslint-config-kibana": "0.0.2",
19 | "@elastic/plugin-helpers": "^6.0.0",
20 | "babel-eslint": "4.1.8",
21 | "eslint": "1.10.3",
22 | "eslint-plugin-mocha": "1.1.0",
23 | "expect.js": "^0.3.1"
24 | },
25 | "dependencies": {
26 | "joi": "^10.6.0",
27 | "lodash": "^4.17.4",
28 | "moment": "^2.18.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/components/notification_center/lib/stored_config.js:
--------------------------------------------------------------------------------
1 | import { has, get, set, isObject } from 'lodash';
2 |
3 | export class StoredConfig {
4 | constructor(defaultConfig = {}) {
5 | this.key = `KBN::NOTIFS::CONFIG`;
6 | this.config = defaultConfig;
7 | };
8 |
9 | has(path) {
10 | return has(this.config, path);
11 | };
12 |
13 | get(path, defaultValue) {
14 | return get(this.config, path, defaultValue);
15 | };
16 |
17 | set(path, value) {
18 | return set(this.config, path, value);
19 | };
20 |
21 | save() {
22 | localStorage.setItem(this.key, JSON.stringify(this.config));
23 | return this;
24 | };
25 |
26 | load() {
27 | try {
28 | const storedConfig = JSON.parse(localStorage.getItem(this.key));
29 | if (isObject(storedConfig)) {
30 | this.config = storedConfig;
31 | }
32 | } catch (e) {
33 | console.warn('Failed to load stored config.\n', e);
34 | }
35 | return this;
36 | };
37 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 sw-jung
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/lib/register_template.js:
--------------------------------------------------------------------------------
1 | import { parseWithWildcard } from './parse_index_pattern';
2 | import { mappings } from './notification_mappings';
3 |
4 | const tags = ['register_template', 'notification_center'];
5 | export function registerTemplate(server) {
6 | const { index, template } = server.config().get('notification_center');
7 | const { waitUntilReady, getCluster } = server.plugins.elasticsearch;
8 | const { callWithInternalUser } = getCluster('admin');
9 |
10 | return waitUntilReady()
11 | .then(() => {
12 | return !template.overwrite && callWithInternalUser('indices.existsTemplate', {
13 | name: template.name,
14 | ignore: 404
15 | })
16 | .then(exists => exists);
17 | })
18 | .then(skip => {
19 | return skip || Promise.resolve()
20 | .then(server.log([...tags, 'info'], `Try to put template '${template.name}'.`))
21 | .then(() => callWithInternalUser('indices.putTemplate', {
22 | name: template.name,
23 | create: !template.overwrite,
24 | body: {
25 | template: parseWithWildcard(index),
26 | mappings
27 | }
28 | }))
29 | .then(resp => server.log([...tags, 'info'], `Success to put template '${template.name}'.`))
30 | .catch(err => server.log([...tags, 'error'], err));
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/server/routes/notification/push.js:
--------------------------------------------------------------------------------
1 | import { extend } from 'lodash';
2 | import Joi from 'joi';
3 | import moment from 'moment';
4 | import { constants } from '../../lib/constants';
5 | import { parseWithTimestamp } from '../../lib/parse_index_pattern';
6 |
7 | export function push(server) {
8 | const index = server.config().get('notification_center.index');
9 | const type = 'notification';
10 | const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
11 |
12 | server.route({
13 | path: `${constants.API_BASE_URL}/notification`,
14 | method: ['POST', 'PUT'],
15 | handler(request, reply) {
16 | const { payload } = request;
17 | const timestamp = Date.now();
18 |
19 | callWithRequest(request, 'index', {
20 | index: parseWithTimestamp(index, moment(timestamp)),
21 | type,
22 | body: extend(payload, { timestamp })
23 | })
24 | .then(resp => {
25 | return reply(constants.RESPONSE.OK);
26 | })
27 | .catch(reply);
28 |
29 | },
30 | config: {
31 | validate: {
32 | payload: Joi.object({
33 | type: Joi.string().only('error', 'warning', 'info').default('info'),
34 | content: Joi.string().required()
35 | })
36 | }
37 | }
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/public/components/notification_center/lib/polling_notifications.js:
--------------------------------------------------------------------------------
1 | import { chain } from 'lodash';
2 | import { getVisible, addBasePath } from 'ui/chrome';
3 | import { addSystemApiHeader } from 'ui/system_api';
4 |
5 | export function pollingNotifications($timeout, $http, NotificationCenter, Notifier) {
6 | if (!getVisible()) {
7 | return;
8 | }
9 |
10 | const { config } = NotificationCenter;
11 | const notify = new Notifier();
12 | $timeout(function pullNotifications() {
13 | return $http.get(addBasePath('/api/notification_center/notification'), {
14 | headers: addSystemApiHeader({}),
15 | params: {
16 | from: config.get('lastPulledAt'),
17 | size: config.get('maxSize')
18 | }
19 | })
20 | .then(({ data }) => {
21 | const notifications = data || [];
22 | const lastPulledAt = chain(notifications)
23 | .forEach(notification => {
24 | notify[notification.type || 'info'](notification.content);
25 | })
26 | .map('timestamp')
27 | .max()
28 | .value();
29 |
30 | if (lastPulledAt > 0) {
31 | config.set('lastPulledAt', lastPulledAt);
32 | config.save();
33 | }
34 | })
35 | .then(() => $timeout(pullNotifications, config.get('pollingInterval')));
36 | }, config.get('pollingInterval'));
37 | };
--------------------------------------------------------------------------------
/public/nav_control/nav_control.js:
--------------------------------------------------------------------------------
1 | import { constant, includes } from 'lodash';
2 | import { element } from 'angular';
3 | import { getInjected } from 'ui/chrome';
4 | import { uiModules } from 'ui/modules';
5 | import { chromeNavControlsRegistry } from 'ui/registry/chrome_nav_controls';
6 | import '../components/notification_center';
7 | import template from './nav_control.html';
8 | import 'ui/angular-bootstrap';
9 |
10 | chromeNavControlsRegistry.register(constant({
11 | name: 'notification_center',
12 | order: 1000,
13 | template
14 | }));
15 |
16 | const supportDarkTheme = getInjected('notificationCenter.supportDarkTheme', true);
17 | const module = uiModules.get('notification_center', []);
18 | module.controller('notificationCenterNavController', ($scope, $compile, $document, NotificationCenter) => {
19 | function initNotificationCenter() {
20 | const $elem = $scope.$notificationCenter = $compile(` `)($scope)
21 | .toggleClass('support-dark-theme', supportDarkTheme)
22 | .appendTo('.app-wrapper-panel');
23 | $document.on('click', () => $elem.hide());
24 | $elem.on('click', e => e.stopPropagation());
25 | };
26 |
27 | $scope.openNotificationCenter = event => {
28 | event.preventDefault();
29 | if (!$scope.$notificationCenter) {
30 | initNotificationCenter();
31 | } else {
32 | $scope.$notificationCenter.toggle();
33 | }
34 | event.stopPropagation();
35 | };
36 |
37 | $scope.formatTooltip = () =>{
38 | return `${NotificationCenter.notifications.length} new notifications`;
39 | };
40 | });
41 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 | import { init } from './server/init';
3 | import { replaceInjectedVars } from './server/lib/replace_injected_vars';
4 |
5 | export default function (kibana) {
6 | return new kibana.Plugin({
7 | id: 'notification_center',
8 | configPrefix: 'notification_center',
9 | require: ['elasticsearch'],
10 | name: 'notification_center',
11 | publicDir: resolve(__dirname, 'public'),
12 | uiExports: {
13 |
14 | chromeNavControls: [
15 | 'plugins/notification_center/nav_control/nav_control'
16 | ],
17 |
18 | injectDefaultVars(server) {
19 | return {
20 | notificationCenter: {
21 | supportDarkTheme: server.config().get('notification_center.supportDarkTheme')
22 | }
23 | };
24 | },
25 |
26 | replaceInjectedVars
27 |
28 | },
29 |
30 | config(Joi) {
31 | return Joi.object({
32 | enabled: Joi.boolean().default(true),
33 | index: Joi.string().default('notification-%{+YYYY.MM.DD}'),
34 | template: Joi.object({
35 | name: Joi.string().default('notification_center_template'),
36 | overwrite: Joi.boolean().default(false)
37 | }).default(),
38 | api: Joi.object({
39 | enabled: Joi.boolean().default(true),
40 | pull: Joi.object({
41 | maxSize: Joi.number().default(100)
42 | }).default()
43 | }).default(),
44 | supportDarkTheme: Joi.boolean().default(true)
45 | }).default();
46 | },
47 |
48 | init
49 |
50 | });
51 | };
--------------------------------------------------------------------------------
/TRANSLATION.md:
--------------------------------------------------------------------------------
1 | A Kibana translation plugin structure.
2 |
3 | The main goal is to keep the plugin extremely simple so non-technical translators will have no trouble
4 | creating new translations for Kibana. Everything except for the translations themselves can be generated
5 | automatically with some enhancements to the Kibana plugin generator. The generator would only need a
6 | plugin name and a list of one or more languages the user wants to create translations for.
7 |
8 | The plugin exports its translation file(s) on the server when it it starts up. This is achieved by publishing the files
9 | via 'uiExports'.This is configurable by modifying the 'translations' item in the 'uiExports'.
10 |
11 | Translation files are broken up by language and must have names that match IETF BCP 47 language codes.
12 | Each translation file contains a single flat object with translation strings matched to their unique keys. Keys are
13 | prefixed with plugin names and a dash to ensure uniqueness between plugins. A translation plugin is not restricted to
14 | providing translations only for itself, the provided translations can cover other plugins as well.
15 |
16 | For example, this template plugin shows how a third party plugin might provide spanish translations for the Kibana core "kibana" app, which is itself a separate plugin.
17 |
18 | To create a translation plugin using this template, follow these steps:
19 | 1. Generate the plugin using the generator
20 | 2. Add your translations files to /translations directory. Remove/Overwrite the existing translation file (i.e. 'es.json').
21 | 3. Edit /index.js, updating the 'translations' item as per your plugin translations.
22 | 4. Restart the Kibana server to publish your plugin translations.
23 |
--------------------------------------------------------------------------------
/server/routes/notification/pull.js:
--------------------------------------------------------------------------------
1 | import { defaults, set, get } from 'lodash';
2 | import Joi from 'joi';
3 | import moment from 'moment';
4 | import { constants } from '../../lib/constants';
5 | import { parseWithWildcard } from '../../lib/parse_index_pattern';
6 |
7 | export function pull(server) {
8 | const index = parseWithWildcard(server.config().get('notification_center.index'));
9 | const type = 'notification';
10 | const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
11 | const { maxSize } = server.config().get('notification_center.api.pull');
12 |
13 | server.route({
14 | path: `${constants.API_BASE_URL}/notification`,
15 | method: 'GET',
16 | handler(request, reply) {
17 | const { size, from, to } = request.query;
18 |
19 | callWithRequest(request, 'search', {
20 | index,
21 | type,
22 | size,
23 | ignoreUnavailable: true,
24 | body: (() => {
25 | const rangeQueries = [];
26 |
27 | if (from) {
28 | rangeQueries.push(set({}, 'range.timestamp.gt', moment(from).valueOf()));
29 | }
30 |
31 | if (to) {
32 | rangeQueries.push(set({}, 'range.timestamp.lt', moment(to).valueOf()));
33 | }
34 |
35 | return rangeQueries.length ? set({}, 'query.bool.must', rangeQueries) : undefined;
36 | })()
37 | })
38 | .then(resp => {
39 | return reply(get(resp, 'hits.hits', []).map(hit => hit._source));
40 | })
41 | .catch(reply);
42 | },
43 | config: {
44 | validate: {
45 | query: Joi.object({
46 | size: Joi.number().min(1).max(maxSize).default(maxSize),
47 | from: Joi.date().optional(),
48 | to: Joi.date().optional()
49 | }).default()
50 | }
51 | }
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/public/components/notification_center/index.less:
--------------------------------------------------------------------------------
1 | @globalTextColor--darkTheme: #cecece;
2 | @globalLinkColor-isHover--darkTheme: #def2f6;
3 | @globalColorMediumGray: #999;
4 | @globalColorDarkGray: #666;
5 | @borderColor--darkTheme: #000;
6 | @backgroundColor--darkTheme: #525252;
7 |
8 | notification-center {
9 | position: fixed!important;
10 | right: 0;
11 | width: 500px;
12 | max-width: ~'calc(100% - 53px)';
13 | height: 100%;
14 | background-color: #fff;
15 | border-left: 1px solid #6EADC1;
16 | box-shadow: 0 5px 22px rgba(0, 0, 0, 0.25);
17 | overflow-y: scroll;
18 | z-index: 100;
19 |
20 | .kuiEventBody__message {
21 | p:last-child {
22 | margin: 0;
23 | };
24 | };
25 |
26 | .notification-stack {
27 | &:last-child {
28 | padding-bottom: 0;
29 | };
30 |
31 | pre {
32 | margin: 0;
33 | };
34 | };
35 |
36 | .theme-dark+&.support-dark-theme {
37 | background-color: @backgroundColor--darkTheme;
38 | border-color: @borderColor--darkTheme;
39 | color: @globalTextColor--darkTheme;
40 |
41 | .kuiView {
42 | background-color: @backgroundColor--darkTheme;
43 | border-color: @borderColor--darkTheme;
44 | }
45 |
46 | .kuiHeaderBar {
47 | border-color: @globalColorDarkGray;
48 | }
49 |
50 | .kuiMenuItem {
51 | border-color: @globalColorDarkGray;
52 | }
53 |
54 | .kuiMenuButton--basic {
55 | background-color: @backgroundColor--darkTheme;
56 | color: @globalTextColor--darkTheme;
57 |
58 | &:hover {
59 | color: #fff!important;
60 | }
61 | }
62 |
63 | .kuiEventBody__message {
64 | color: @globalTextColor--darkTheme;
65 | }
66 |
67 | .kuiEventBody__metadata {
68 | color: @globalColorMediumGray;
69 | }
70 |
71 | .well {
72 | background-color: @globalColorDarkGray;
73 | color: @globalTextColor--darkTheme;
74 | }
75 | }
76 | };
--------------------------------------------------------------------------------
/public/components/notification_center/lib/stored_notifications.js:
--------------------------------------------------------------------------------
1 | import { find, isArray, remove, chain, pick, defaults, union } from 'lodash';
2 |
3 | const NOT_INSERTABLE_ERROR = new Error('StoredNotifications cannot insert directly. Please use `StoredNotifications.merge`.');
4 | export class StoredNotifications extends Array {
5 | constructor() {
6 | super();
7 | this.key = `KBN::NOTIFS`;
8 |
9 | this.push = (...items) => {
10 | throw NOT_INSERTABLE_ERROR;
11 | };
12 |
13 | this.unshift = (...items) => {
14 | throw NOT_INSERTABLE_ERROR;
15 | };
16 |
17 | this.merge = (...items) => {
18 | const timestamp = new Date().valueOf();
19 | const freshItems = chain(items)
20 | .filter(item => {
21 | const mergeable = find(this, pick(item, ['type', 'content']));
22 | if (mergeable) {
23 | mergeable.timestamp = item.timestamp;
24 | mergeable.count += (item.count || 1);
25 | mergeable.stacks = union(mergeable.stacks, item.stacks);
26 | }
27 | return !mergeable;
28 | })
29 | .map(item => defaults(item, { timestamp, count: 1 }))
30 | .value();
31 | const result = super.push(...freshItems);
32 | this.save();
33 | return result;
34 | };
35 |
36 | this.remove = (predicate) => {
37 | const result = remove(this, predicate);
38 | this.save();
39 | return result;
40 | };
41 |
42 | this.save = () => {
43 | const requiredFields = ['timestamp', 'type', 'content', 'stack', 'stacks', 'title', 'icon', 'count'];
44 | const items = this.map(item => pick(item,requiredFields));
45 | localStorage.setItem(this.key, JSON.stringify(items));
46 | return this;
47 | };
48 |
49 | this.load = () => {
50 | try {
51 | const storedList = JSON.parse(localStorage.getItem(this.key));
52 | if (isArray(storedList)) {
53 | this.length = 0;
54 | this.merge(...storedList);
55 | }
56 | } catch (e) {
57 | console.warn('Failed to load stored notifications.\n', e);
58 | }
59 | return this;
60 | };
61 |
62 | this.isEmpty = () => {
63 | return this.length === 0;
64 | };
65 | };
66 |
67 | };
68 |
--------------------------------------------------------------------------------
/public/components/notification_center/index.js:
--------------------------------------------------------------------------------
1 | import { chain } from 'lodash';
2 | import { element } from 'angular';
3 | import moment from 'moment-timezone';
4 | import { uiModules } from 'ui/modules';
5 | import { notify } from 'ui/notify';
6 | import { StoredNotifications } from './lib/stored_notifications';
7 | import { StoredConfig } from './lib/stored_config';
8 | import { pollingNotifications } from './lib/polling_notifications';
9 | import { getNotificationClasses } from './lib/get_notification_classes';
10 | import template from './template.html';
11 | import './index.less';
12 |
13 | const module = uiModules.get('notification_center', []);
14 |
15 | module.run(pollingNotifications);
16 |
17 | module.service('NotificationCenter', () => {
18 | const notifications = new StoredNotifications().load();
19 | const config = new StoredConfig({
20 | pollingInterval: 10000,
21 | lastPulledAt: Date.now()
22 | }).load().save();
23 |
24 | return {
25 | notifications,
26 | config
27 | };
28 | });
29 |
30 | module.directive('notificationCenter', (config, NotificationCenter, $filter) => {
31 | return {
32 | restrict: 'E',
33 | template,
34 | controller: ($scope) => {
35 | const notifs = $scope.notifs = NotificationCenter.notifications;
36 | $scope.$watchCollection(() => notify._notifs, change => {
37 | const timestamp = new Date().valueOf();
38 | const newNotifs = chain(change)
39 | .filter(notif => !notif.timestamp)
40 | .forEach(notif => notif.timestamp = timestamp)
41 | .value();
42 | if (newNotifs.length) {
43 | notifs.merge(...newNotifs);
44 | }
45 | });
46 |
47 | config.watch('dateFormat', setDateFormat, $scope);
48 | config.watch('dateFormat:tz', setDefaultTimezone, $scope);
49 | config.watch('dateFormat:dow', setStartDayOfWeek, $scope);
50 |
51 | function setDateFormat(format) {
52 | $scope.dateFormat = format;
53 | };
54 |
55 | function setDefaultTimezone(tz) {
56 | moment.tz.setDefault(tz);
57 | }
58 |
59 | function setStartDayOfWeek(day) {
60 | const dow = moment.weekdays().indexOf(day);
61 | moment.updateLocale(moment.locale(), { week: { dow } });
62 | }
63 |
64 | setDateFormat(config.get('dateFormat'));
65 |
66 | $scope.moment = moment;
67 | $scope.getNotificationClasses = getNotificationClasses;
68 | }
69 | };
70 | });
71 |
--------------------------------------------------------------------------------
/public/components/notification_center/template.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notification Center
2 |
3 | Notification Center is a kibana plugin for better experience of notifier toasts.
4 |
5 | 
6 |
7 | ## Features
8 |
9 | ### Webhook Endpoint
10 |
11 | Provides a webhook endpoint that can generate a notification toast.
12 |
13 | This feature is useful with the x-pack alerting.
14 |
15 | * See [API](#api) for usage.
16 |
17 | ### Notification History
18 |
19 | You can see notification history in the notification center.(It's appear when you click the navbar button.)
20 |
21 | This feature is useful when there are notifications that not be checked, such as when you are not looking at the screen.
22 |
23 | In particular, you can check notifications received via [webhook](#webhook-endpoint) when you are not connected.
24 |
25 | Of course, you can clear the checked notification.
26 |
27 | > **NOTE: Not only notifications pushed through api, all notifications of generated by kibana's `Notifier` are displayed in notification center panel.**
28 |
29 | ## Installation
30 |
31 | Copy installation file's url as same version of your kibana from [the repository releases](https://github.com/sw-jung/kibana_notification_center/releases).
32 |
33 | And
34 | ```bash
35 | $ cd path/to/your/kibana
36 | $ bin/kibana-plugin install
37 | ```
38 |
39 | If you want more information, See [this document](https://www.elastic.co/guide/en/kibana/current/_installing_plugins.html).
40 |
41 | > **NOTE: How can I do if I cannot find version I want?**
42 | > I'm sorry for that. This plugin not all of kibana versions are supported.
43 | > If you need unsupported version, please test nearest version.
44 | > In this case, you need to modify the kibana.version field in package.json.
45 | > **Currently minimum support version is v5.4.3**
46 |
47 | ## Configuration
48 |
49 | The following configurable options are set by default.
50 |
51 | ### kibana.yml
52 |
53 | ```yml
54 | notification_center:
55 | enabled: true
56 |
57 | # Name of the index where pushed notification is stored.
58 | # You can use momentjs's date format in wrapped by `%{}`. See https://momentjs.com/docs/#/displaying/format/
59 | index: "notification-%{+YYYY.MM.DD}"
60 |
61 | # The index template is created when you start kibana.
62 | template:
63 | name: "notification_center_template"
64 | overwrite: false
65 |
66 | api:
67 | # If `false` then disable all of REST APIs.
68 | enabled: true
69 | # The maximum size of the notification that user can pull from the server in a single request.
70 | pull.maxSize: 100
71 |
72 | supportDarkTheme: true
73 | ```
74 |
75 | ## API
76 |
77 | ### Push notification
78 |
79 | * **URL**: /api/notification_center/notification
80 | * **Method**: POST|PUT
81 | * **Headers**
82 | ```javascript
83 | {
84 | "Authorization": "your credential token here", // If you use x-pack security.
85 | "Content-Type": "application/json",
86 | "kbn-version": "your kibana version here" // This is kibana spec. ex: 5.6.0
87 | }
88 | ```
89 |
90 | * **Data Params**
91 | ```javascript
92 | {
93 | "type": "error|warning|info" // Optional. Default "info"
94 | "content": "Write your notification content here." // Required
95 | }
96 | ```
97 |
98 | * **Response**
99 | ```javascript
100 | // 200 OK
101 | {
102 | "acknowledged": true
103 | }
104 |
105 | // The others are error.
106 | ```
107 | ---
108 |
109 | ## development
110 |
111 | See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. Once you have completed that, use the following npm tasks.
112 |
113 | - `npm start`
114 |
115 | Start kibana and have it include this plugin
116 |
117 | - `npm start -- --config kibana.yml`
118 |
119 | You can pass any argument that you would normally send to `bin/kibana` by putting them after `--` when running `npm start`
120 |
121 | - `npm run build`
122 |
123 | Build a distributable archive
124 |
125 | - `npm run test:browser`
126 |
127 | Run the browser tests in a real web browser
128 |
129 | - `npm run test:server`
130 |
131 | Run the server tests using mocha
132 |
133 | For more information about any of these commands run `npm run ${task} -- --help`.
134 |
--------------------------------------------------------------------------------