mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
Initial phase of notification service.
This commit is contained in:
parent
b4a04b3a80
commit
fb0814fcfd
17 changed files with 820 additions and 40 deletions
|
@ -22,9 +22,12 @@
|
|||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
<li>
|
||||
<a ng-click="configure()"><i class="fa fa-gear"></i></a>
|
||||
<li ng-repeat="level in levels">
|
||||
<a ng-click="notifTest(level.name)" tooltip="{{level.name}}">
|
||||
<i class="fa" ng-class="'fa-' + level.icon"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li><a ng-click="configure()" tooltip="Configure Kibana"><i class="fa fa-gear"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -34,7 +37,7 @@
|
|||
config-submit="saveOpts">
|
||||
</config>
|
||||
<div class="application" kbn-view></div>
|
||||
|
||||
</div>
|
||||
<div kbn-notifications list="notifList"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -6,10 +6,19 @@ define(function (require) {
|
|||
require('services/config');
|
||||
require('services/courier');
|
||||
require('directives/view');
|
||||
require('angular-bootstrap');
|
||||
|
||||
require('modules')
|
||||
.get('kibana/controllers')
|
||||
.controller('kibana', function ($scope, courier, config, configFile) {
|
||||
.get('kibana/controllers', ['ui.bootstrap'])
|
||||
.config(function ($tooltipProvider) {
|
||||
$tooltipProvider.options({
|
||||
placement: 'bottom',
|
||||
animation: true,
|
||||
popupDelay: 150,
|
||||
appendToBody: false
|
||||
});
|
||||
})
|
||||
.controller('kibana', function ($scope, courier, config, configFile, notify, $timeout) {
|
||||
$scope.apps = configFile.apps;
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function (event, uri) {
|
||||
|
@ -38,6 +47,38 @@ define(function (require) {
|
|||
$scope.configureTemplateUrl = require('text!../partials/global_config.html');
|
||||
};
|
||||
|
||||
// expose the notification services list of notifs on the $scope so that the
|
||||
// notification directive can show them on the screen
|
||||
$scope.notifList = notify._notifs;
|
||||
// provide alternate methods for setting timeouts, which will properly trigger digest cycles
|
||||
notify._setTimerFns($timeout, $timeout.cancel);
|
||||
|
||||
(function TODO_REMOVE() {
|
||||
// stuff for testing notifications
|
||||
$scope.levels = [
|
||||
{ name: 'info', icon: 'info' },
|
||||
{ name: 'warning', icon: 'info-circle' },
|
||||
{ name: 'error', icon: 'warning' },
|
||||
{ name: 'fatal', icon: 'fire' },
|
||||
];
|
||||
$scope.notifTest = function (type) {
|
||||
var arg = 'Something happened, just thought you should know.';
|
||||
var cb;
|
||||
if (type === 'fatal' || type === 'error') {
|
||||
arg = new Error('Ah fuck');
|
||||
}
|
||||
if (type === 'error') {
|
||||
cb = function (resp) {
|
||||
if (resp !== 'report') return;
|
||||
$timeout(function () {
|
||||
notify.info('Report sent, thank you for your help.');
|
||||
}, 750);
|
||||
};
|
||||
}
|
||||
notify[type](arg, cb);
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* Persist current settings
|
||||
* @return {[type]} [description]
|
||||
|
@ -71,6 +112,13 @@ define(function (require) {
|
|||
config.$watch('refreshInterval', $scope.setFetchInterval);
|
||||
$scope.$watch('opts.activeFetchInterval', $scope.setFetchInterval);
|
||||
|
||||
|
||||
// setup the courier
|
||||
courier.on('error', function (err) {
|
||||
$scope[$scope.$$phase ? '$eval' : '$apply'](function () {
|
||||
notify.error(err);
|
||||
});
|
||||
});
|
||||
$scope.$on('application.load', function () {
|
||||
courier.start();
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ define(function (require) {
|
|||
var setup = require('./setup');
|
||||
var configFile = require('../config');
|
||||
var modules = require('modules');
|
||||
var notify = require('notify/notify');
|
||||
|
||||
require('elasticsearch');
|
||||
require('angular-route');
|
||||
|
@ -46,8 +47,14 @@ define(function (require) {
|
|||
return 'apps/' + app.id + '/index';
|
||||
})), function bootstrap() {
|
||||
$(function () {
|
||||
angular.bootstrap(document, ['kibana']);
|
||||
$(document.body).children().show();
|
||||
notify.lifecycle('bootstrap');
|
||||
angular
|
||||
.bootstrap(document, ['kibana'])
|
||||
.invoke(function (notify) {
|
||||
notify.lifecycle('bootstrap', true);
|
||||
$(document.body).children().show();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
135
src/kibana/notify/directives.js
Normal file
135
src/kibana/notify/directives.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
define(function (require) {
|
||||
var notify = require('modules').get('notify');
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var MutableWatcher = require('utils/mutable_watcher');
|
||||
var nextTick = require('utils/next_tick');
|
||||
|
||||
var defaultToastOpts = {
|
||||
title: 'Notice',
|
||||
lifetime: 7000
|
||||
};
|
||||
|
||||
var transformKey = (function () {
|
||||
var el = document.createElement('div');
|
||||
return _.find(['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform'], function (key) {
|
||||
return el.style[key] !== void 0;
|
||||
});
|
||||
}());
|
||||
|
||||
notify.directive('kbnNotifications', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
list: '=list'
|
||||
},
|
||||
template: require('text!./partials/toaster.html'),
|
||||
link: function ($scope, $el) {
|
||||
|
||||
$el.addClass('toaster-container');
|
||||
|
||||
// handles recalculating positions and offsets, schedules
|
||||
// recalcs and waits for 100 seconds before running again.
|
||||
var layoutList = (function () {
|
||||
// lazy load the $nav element
|
||||
var navSelector = '.content > nav.navbar:first()';
|
||||
var $nav;
|
||||
|
||||
// pixels between the top of list and it's attachment(nav/window)
|
||||
var spacing = 10;
|
||||
// was the element set to postition: fixed last calc?
|
||||
|
||||
var visible = false;
|
||||
|
||||
var recalc = function () {
|
||||
// set $nav lazily
|
||||
if (!$nav || !$nav.length) $nav = $(navSelector);
|
||||
|
||||
// if we can't find the nav, don't display the list
|
||||
if (!$nav.length) return;
|
||||
|
||||
// the top point at which the list should be secured
|
||||
var fixedTop = $nav.height();
|
||||
|
||||
// height of the section at the top of the page that is hidden
|
||||
var hiddenBottom = document.body.scrollTop;
|
||||
|
||||
var top, left, css = {
|
||||
visibility: 'visible'
|
||||
};
|
||||
|
||||
if (hiddenBottom > fixedTop) {
|
||||
// if we are already fixed, no reason to set the styles again
|
||||
css.position = 'fixed';
|
||||
top = spacing;
|
||||
} else {
|
||||
css.position = 'absolute';
|
||||
top = fixedTop + spacing;
|
||||
}
|
||||
|
||||
// calculate the expected left value (keep it centered)
|
||||
left = Math.floor((document.body.scrollWidth - $el.width()) / 2);
|
||||
css[transformKey] = 'translateX(' + Math.round(left) + 'px) translateY(' + Math.round(top) + 'px)';
|
||||
if (transformKey !== 'msTransform') {
|
||||
// The Z transform will keep this in the GPU (faster, and prevents artifacts),
|
||||
// but IE9 doesn't support 3d transforms and will choke.
|
||||
css[transformKey] += ' translateZ(0)';
|
||||
}
|
||||
|
||||
$el.css(css);
|
||||
};
|
||||
|
||||
// track the already scheduled recalcs
|
||||
var timeoutId;
|
||||
var clearSchedule = function () {
|
||||
timeoutId = null;
|
||||
};
|
||||
|
||||
var schedule = function () {
|
||||
if (timeoutId) return;
|
||||
else recalc();
|
||||
timeoutId = setTimeout(clearSchedule, 25);
|
||||
};
|
||||
|
||||
// call to remove the $el from the view
|
||||
schedule.hide = function () {
|
||||
$el.css('visibility', 'hidden');
|
||||
visible = false;
|
||||
};
|
||||
|
||||
return schedule;
|
||||
}());
|
||||
|
||||
function listen(off) {
|
||||
$(window)[off ? 'off' : 'on']('resize scroll', layoutList);
|
||||
}
|
||||
|
||||
var wat = new MutableWatcher({
|
||||
$scope: $scope,
|
||||
expression: 'list',
|
||||
type: 'collection'
|
||||
}, showList);
|
||||
|
||||
function showList(list) {
|
||||
if (list && list.length) {
|
||||
listen();
|
||||
wat.set(hideList);
|
||||
|
||||
// delay so that angular has time to update the DOM
|
||||
nextTick(layoutList);
|
||||
}
|
||||
}
|
||||
|
||||
function hideList(list) {
|
||||
if (!list || !list.length) {
|
||||
listen(true);
|
||||
wat.set(showList);
|
||||
layoutList.hide();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('$destoy', _.partial(listen, true));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
72
src/kibana/notify/errors.js
Normal file
72
src/kibana/notify/errors.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
define(function (require) {
|
||||
var errors = {};
|
||||
var _ = require('lodash');
|
||||
var inherits = require('utils/inherits');
|
||||
|
||||
var canStack = (function () {
|
||||
var err = new Error();
|
||||
return !!err.stack;
|
||||
}());
|
||||
|
||||
// abstract error class
|
||||
function KibanaError(msg, constructor) {
|
||||
this.message = msg;
|
||||
|
||||
Error.call(this, this.message);
|
||||
if (!this.stack) {
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, constructor || KibanaError);
|
||||
} else if (canStack) {
|
||||
this.stack = (new Error()).stack;
|
||||
} else {
|
||||
this.stack = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
errors.KibanaError = KibanaError;
|
||||
inherits(KibanaError, Error);
|
||||
|
||||
/**
|
||||
* Map of error text for different error types
|
||||
* @type {Object}
|
||||
*/
|
||||
var requireTypeText = {
|
||||
timeout: 'a network timeout',
|
||||
nodefine: 'an invalid module definition',
|
||||
scripterror: 'a generic script error'
|
||||
};
|
||||
|
||||
/**
|
||||
* ScriptLoadFailure error class for handling requirejs load failures
|
||||
* @param {String} [msg] -
|
||||
*/
|
||||
errors.ScriptLoadFailure = function ScriptLoadFailure(err) {
|
||||
var explain = requireTypeText[err.requireType] || err.requireType || 'an unknown error';
|
||||
|
||||
this.stack = err.stack;
|
||||
var modules = err.requireModules;
|
||||
if (_.isArray(modules) && modules.length > 0) {
|
||||
modules = modules.map(JSON.stringify);
|
||||
|
||||
if (modules.length > 1) {
|
||||
modules = modules.slice(0, -1).join(', ') + ' and ' + modules.slice(-1)[0];
|
||||
} else {
|
||||
modules = modules[0];
|
||||
}
|
||||
|
||||
modules += ' modules';
|
||||
}
|
||||
|
||||
if (!modules || !modules.length) {
|
||||
modules = 'unknown modules';
|
||||
}
|
||||
|
||||
|
||||
KibanaError.call(this,
|
||||
'Unable to load ' + modules + ' because of ' + explain + '.',
|
||||
errors.ScriptLoadFailure);
|
||||
};
|
||||
inherits(errors.ScriptLoadFailure, KibanaError);
|
||||
|
||||
return errors;
|
||||
});
|
163
src/kibana/notify/manager.js
Normal file
163
src/kibana/notify/manager.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
|
||||
var fatalToastTemplate = (function lazyTemplate(tmpl) {
|
||||
var compiled;
|
||||
return function (vars) {
|
||||
compiled = compiled || _.template(tmpl);
|
||||
return compiled(vars);
|
||||
};
|
||||
}(require('text!./partials/fatal.html')));
|
||||
|
||||
/**
|
||||
* Functionality to check that
|
||||
*/
|
||||
function NotifyManager() {
|
||||
|
||||
var applicationBooted;
|
||||
var notifs = this._notifs = [];
|
||||
var setTO = setTimeout;
|
||||
var clearTO = clearTimeout;
|
||||
|
||||
function now() {
|
||||
if (window.performance && window.performance.now) {
|
||||
return window.performance.now();
|
||||
}
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
var log = (typeof KIBANA_DIST === 'undefined') ? _.bindKey(console, 'log') : _.noop;
|
||||
|
||||
function closeNotif(cb, key) {
|
||||
return function () {
|
||||
// this === notif
|
||||
var i = notifs.indexOf(this);
|
||||
if (i !== -1) notifs.splice(i, 1);
|
||||
if (this.timerId) this.timerId = clearTO(this.timerId);
|
||||
if (typeof cb === 'function') cb(key);
|
||||
};
|
||||
}
|
||||
|
||||
function add(notif, cb) {
|
||||
if (notif.lifetime !== Infinity) {
|
||||
notif.timerId = setTO(function () {
|
||||
closeNotif(cb, 'ignore').call(notif);
|
||||
}, notif.lifetime);
|
||||
}
|
||||
|
||||
if (notif.actions) {
|
||||
notif.actions.forEach(function (action) {
|
||||
notif[action] = closeNotif(cb, action);
|
||||
});
|
||||
}
|
||||
|
||||
notifs.push(notif);
|
||||
}
|
||||
|
||||
this._setTimerFns = function (set, clear) {
|
||||
setTO = set;
|
||||
clearTO = clear;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify the serivce of app lifecycle events
|
||||
* @type {[type]}
|
||||
*/
|
||||
var lifecycleEvents = window.kibanaLifecycleEvents = {};
|
||||
this.lifecycle = function (name, success) {
|
||||
var status;
|
||||
if (name === 'bootstrap' && success === true) applicationBooted = true;
|
||||
|
||||
if (success === void 0) {
|
||||
// start
|
||||
lifecycleEvents[name] = now();
|
||||
} else {
|
||||
// end
|
||||
if (success) {
|
||||
lifecycleEvents[name] = now() - (lifecycleEvents[name] || 0);
|
||||
status = lifecycleEvents[name].toFixed(2) + ' ms';
|
||||
} else {
|
||||
lifecycleEvents[name] = false;
|
||||
status = 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
log('KBN: ' + name + (status ? ' - ' + status : ''));
|
||||
};
|
||||
|
||||
/**
|
||||
* Kill the page, and display an error
|
||||
* @param {Error} err - The fatal error that occured
|
||||
*/
|
||||
this.fatal = function (err) {
|
||||
var html = fatalToastTemplate({
|
||||
msg: err instanceof Error ? err.message : err,
|
||||
stack: err.stack
|
||||
});
|
||||
|
||||
var $container = $('#fatal-splash-screen');
|
||||
if ($container.size()) {
|
||||
$container.append(html);
|
||||
return;
|
||||
}
|
||||
|
||||
$container = $();
|
||||
|
||||
// in case the app has not completed boot
|
||||
$(document.body)
|
||||
.removeAttr('ng-cloak')
|
||||
.html('<div id="fatal-splash-screen" class="container-fuild">' + html + '</div>');
|
||||
};
|
||||
|
||||
/**
|
||||
* Alert the user of an error that occured
|
||||
* @param {Error|String} err
|
||||
*/
|
||||
this.error = function (err, cb) {
|
||||
add({
|
||||
type: 'danger',
|
||||
content: err instanceof Error ? err.message : err,
|
||||
icon: 'warning',
|
||||
title: 'Error',
|
||||
lifetime: Infinity,
|
||||
actions: ['report', 'accept']
|
||||
}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Warn the user abort something
|
||||
* @param {[type]} msg [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
this.warning = function (msg, cb) {
|
||||
add({
|
||||
type: 'warning',
|
||||
content: msg,
|
||||
icon: 'warning',
|
||||
title: 'Warning',
|
||||
lifetime: 7000,
|
||||
actions: ['accept']
|
||||
}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a debug message
|
||||
* @param {String} msg [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
this.info = function (msg, cb) {
|
||||
add({
|
||||
type: 'info',
|
||||
content: msg,
|
||||
icon: 'info-circle',
|
||||
title: 'Debug',
|
||||
lifetime: 7000,
|
||||
actions: ['accept']
|
||||
}, cb);
|
||||
};
|
||||
}
|
||||
|
||||
return NotifyManager;
|
||||
|
||||
});
|
93
src/kibana/notify/notify.js
Normal file
93
src/kibana/notify/notify.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var $ = require('jquery');
|
||||
var modules = require('modules');
|
||||
var module = modules.get('notify');
|
||||
var errors = require('./errors');
|
||||
var NotifyManager = require('./manager');
|
||||
var manager = new NotifyManager();
|
||||
|
||||
require('./directives');
|
||||
|
||||
module.service('notify', function () {
|
||||
var service = this;
|
||||
// modify the service to have bound proxies to the manager
|
||||
_.forOwn(manager, function (val, key) {
|
||||
service[key] = typeof val === 'function' ? _.bindKey(manager, key) : val;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Global Angular uncaught exception handler
|
||||
*/
|
||||
modules
|
||||
.get('exceptionOverride')
|
||||
.factory('$exceptionHandler', function () {
|
||||
return function (exception, cause) {
|
||||
manager.fatal(exception, cause);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Global Require.js exception handler
|
||||
*/
|
||||
window.requirejs.onError = function (err) {
|
||||
manager.fatal(new errors.ScriptLoadFailure(err));
|
||||
};
|
||||
|
||||
window.onerror = function (err, url, line) {
|
||||
manager.fatal(new Error(err + ' (' + url + ':' + line + ')'));
|
||||
return true;
|
||||
};
|
||||
|
||||
// function onTabFocus(onChange) {
|
||||
// var current = true;
|
||||
// // bind each individually
|
||||
// var elem = window;
|
||||
// var focus = 'focus';
|
||||
// var blur = 'blur';
|
||||
|
||||
// if (/*@cc_on!@*/false) { // check for Internet Explorer
|
||||
// elem = document;
|
||||
// focus = 'focusin';
|
||||
// blur = 'focusout';
|
||||
// }
|
||||
|
||||
// function handler(event) {
|
||||
// var state;
|
||||
|
||||
// if (event.type === focus) {
|
||||
// state = true;
|
||||
// } else if (event.type === blur) {
|
||||
// state = false;
|
||||
// } else {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (current !== state) {
|
||||
// current = state;
|
||||
// onChange(current);
|
||||
// }
|
||||
// }
|
||||
|
||||
// elem.addEventListener(focus, handler);
|
||||
// elem.addEventListener(blur, handler);
|
||||
|
||||
// // call the handler ASAP with the current status
|
||||
// nextTick(handler, current);
|
||||
|
||||
// // function that the user can call to unbind this handler
|
||||
// return function unBind() {
|
||||
// elem.removeEventListener(focus, handler);
|
||||
// elem.removeEventListener(blur, handler);
|
||||
// };
|
||||
// }
|
||||
|
||||
// onTabFocus(function (focused) {
|
||||
// // log(focused ? 'welcome back' : 'good bye');
|
||||
// });
|
||||
|
||||
return manager;
|
||||
|
||||
});
|
20
src/kibana/notify/partials/fatal.html
Normal file
20
src/kibana/notify/partials/fatal.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!--
|
||||
!!!!
|
||||
Since fatal error could prevent angular from starting
|
||||
this template is just a simple lodash template
|
||||
!!!!
|
||||
-->
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
<h1 class="panel-title">
|
||||
<i class="fa fa-warning"></i> Fatal Error
|
||||
<a class="pull-right" onclick="window.location.reload();" href="#">
|
||||
Reload <i class="refresh fa fa-refresh"></i>
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="panel-body"><%- msg %></div>
|
||||
<% if (stack) { %>
|
||||
<div class="panel-footer"><pre><%- stack %></pre></div>
|
||||
<% } %>
|
||||
</div>
|
40
src/kibana/notify/partials/toaster.html
Normal file
40
src/kibana/notify/partials/toaster.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<ul class="toaster">
|
||||
<li ng-repeat="notif in list" kbn-toast notif="notif">
|
||||
<div class="alert" ng-class="'alert-' + notif.type">
|
||||
<table><tr>
|
||||
<td>
|
||||
<i class="fa" ng-class="'fa-' + notif.icon" tooltip="{{notif.title}}"></i> {{ notif.content }}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.report"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
data-dismiss="alert"
|
||||
aria-hidden="true"
|
||||
ng-click="notif.report()"
|
||||
>Report</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.accept"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
data-dismiss="alert"
|
||||
aria-hidden="true"
|
||||
ng-click="notif.accept()"
|
||||
>OK</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.address"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
data-dismiss="alert"
|
||||
aria-hidden="true"
|
||||
ng-click="notif.address()"
|
||||
>Fix it</button>
|
||||
</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
|
@ -6,6 +6,7 @@ require.config({
|
|||
angular: '../bower_components/angular/angular',
|
||||
'angular-mocks': '../bower_components/angular-mocks/angular-mocks',
|
||||
'angular-route': '../bower_components/angular-route/angular-route',
|
||||
'angular-bootstrap': '../bower_components/angular-bootstrap/ui-bootstrap-tpls',
|
||||
async: '../bower_components/async/lib/async',
|
||||
css: '../bower_components/require-css/css',
|
||||
text: '../bower_components/requirejs-text/text',
|
||||
|
@ -23,18 +24,11 @@ require.config({
|
|||
deps: ['jquery'],
|
||||
exports: 'angular'
|
||||
},
|
||||
gridster: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
'angular-route': {
|
||||
deps: ['angular']
|
||||
},
|
||||
'angular-mocks': {
|
||||
deps: ['angular']
|
||||
},
|
||||
'elasticsearch': {
|
||||
deps: ['angular']
|
||||
}
|
||||
gridster: ['jquery'],
|
||||
'angular-route': ['angular'],
|
||||
'angular-mocks': ['angular'],
|
||||
'elasticsearch': ['angular'],
|
||||
'angular-bootstrap': ['angular']
|
||||
},
|
||||
waitSeconds: 60
|
||||
});
|
|
@ -2,6 +2,7 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var configFile = require('../../config');
|
||||
var notify = require('notify/notify');
|
||||
|
||||
require('services/courier');
|
||||
|
||||
|
@ -40,17 +41,17 @@ define(function (require) {
|
|||
******/
|
||||
|
||||
function init() {
|
||||
notify.lifecycle('config init');
|
||||
var defer = $q.defer();
|
||||
courier.fetch();
|
||||
doc.fetch();
|
||||
doc.on('results', function completeInit(resp) {
|
||||
// ONLY ACT IF !resp.found
|
||||
if (!resp.found) {
|
||||
console.log('creating empty config doc');
|
||||
doc.doIndex({});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('fetched config doc');
|
||||
notify.lifecycle('config init', !!resp);
|
||||
doc.removeListener('results', completeInit);
|
||||
defer.resolve();
|
||||
});
|
||||
|
@ -87,7 +88,7 @@ define(function (require) {
|
|||
// probably a horrible idea
|
||||
if (!watchers[key]) watchers[key] = [];
|
||||
watchers[key].push(onChange);
|
||||
_notify(onChange, vals[key]);
|
||||
triggerWatchers(onChange, vals[key]);
|
||||
return function un$watcher() {
|
||||
_.pull(watchers[key], onChange);
|
||||
};
|
||||
|
@ -143,15 +144,15 @@ define(function (require) {
|
|||
*******/
|
||||
|
||||
function _change(key, val) {
|
||||
_notify(watchers[key], val, vals[key]);
|
||||
notify.lifecycle('config change: ' + key + ': ' + vals[key] + ' -> ' + val);
|
||||
triggerWatchers(watchers[key], val, vals[key]);
|
||||
vals[key] = val;
|
||||
console.log(key, 'is now', val);
|
||||
}
|
||||
|
||||
function _notify(fns, cur, prev) {
|
||||
function triggerWatchers(fns, cur, prev) {
|
||||
if ($rootScope.$$phase) {
|
||||
// reschedule for next tick
|
||||
nextTick(_notify, fns, cur, prev);
|
||||
nextTick(triggerWatchers, fns, cur, prev);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ define(function (require) {
|
|||
|
||||
var es; // share the client amoungst all apps
|
||||
require('modules')
|
||||
.get('kibana/services')
|
||||
.get('kibana/services', ['elasticsearch'])
|
||||
.service('es', function (esFactory, configFile, $q) {
|
||||
if (es) return es;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ define(function (require) {
|
|||
var angular = require('angular');
|
||||
var async = require('async');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var configFile = require('../config');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var modules = require('modules');
|
||||
|
@ -15,30 +16,31 @@ define(function (require) {
|
|||
return function prebootSetup(done) {
|
||||
// load angular deps
|
||||
require([
|
||||
'kibana',
|
||||
'notify/notify',
|
||||
|
||||
'elasticsearch',
|
||||
'services/es',
|
||||
'services/config',
|
||||
'constants/base'
|
||||
], function (kibana) {
|
||||
], function (notify) {
|
||||
|
||||
$(function () {
|
||||
// create the setup module, it should require the same things
|
||||
// that kibana currently requires, which should only include the
|
||||
// loaded modules
|
||||
var setup = modules.get('setup', ['elasticsearch']);
|
||||
var setup = modules.get('setup');
|
||||
var appEl = document.createElement('div');
|
||||
var kibanaIndexExists;
|
||||
|
||||
modules.link(setup);
|
||||
setup
|
||||
.value('configFile', configFile);
|
||||
|
||||
angular
|
||||
.bootstrap(appEl, ['setup'])
|
||||
.invoke(function (es, config) {
|
||||
.invoke(function (es, config, notify) {
|
||||
// init the setup module
|
||||
async.series([
|
||||
async.apply(checkForES, es),
|
||||
async.apply(checkForKibanaIndex, es),
|
||||
async.apply(createKibanaIndex, es),
|
||||
async.apply(checkForCurrentConfigDoc, es),
|
||||
|
@ -50,25 +52,52 @@ define(function (require) {
|
|||
// linked modules should no longer depend on this module
|
||||
setup.close();
|
||||
|
||||
console.log('booting kibana');
|
||||
if (err) throw err;
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
|
||||
function wrapError(err, tmpl) {
|
||||
// if we pass a callback
|
||||
if (typeof err === 'function') {
|
||||
var cb = err; // wrap it
|
||||
return function (err) {
|
||||
cb(wrapError(err, tmpl));
|
||||
};
|
||||
}
|
||||
|
||||
// if an error didn't actually occur
|
||||
if (!err) return void 0;
|
||||
|
||||
var err2 = new Error(_.template(tmpl, { configFile: configFile }));
|
||||
err2.origError = err;
|
||||
return err2;
|
||||
}
|
||||
|
||||
function checkForES(es, done) {
|
||||
notify.lifecycle('es check');
|
||||
es.ping(function (err, alive) {
|
||||
notify.lifecycle('es check', alive);
|
||||
done(alive ? void 0 : new Error('Unable to connect to Elasticsearch at "' + configFile.elasticsearch + '"'));
|
||||
});
|
||||
}
|
||||
|
||||
function checkForKibanaIndex(es, done) {
|
||||
notify.lifecycle('kibana index check');
|
||||
es.indices.exists({
|
||||
index: configFile.kibanaIndex
|
||||
}, function (err, exists) {
|
||||
console.log('kibana index does', (exists ? '' : 'not ') + 'exist');
|
||||
notify.lifecycle('kibana index check', !!exists);
|
||||
kibanaIndexExists = exists;
|
||||
return done(err);
|
||||
done(wrapError(err, 'Unable to check for Kibana index "<%= configFile.kibanaIndex %>"'));
|
||||
});
|
||||
}
|
||||
|
||||
// create the index if it doens't exist already
|
||||
function createKibanaIndex(es, done) {
|
||||
if (kibanaIndexExists) return done();
|
||||
console.log('creating kibana index');
|
||||
|
||||
notify.lifecycle('create kibana index');
|
||||
es.indices.create({
|
||||
index: configFile.kibanaIndex,
|
||||
body: {
|
||||
|
@ -88,19 +117,20 @@ define(function (require) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, done);
|
||||
}, function (err) {
|
||||
notify.lifecycle('create kibana index', !err);
|
||||
done(wrapError(err, 'Unable to create Kibana index "<%= configFile.kibanaIndex %>"'));
|
||||
});
|
||||
}
|
||||
|
||||
// if the index is brand new, no need to see if it is out of data
|
||||
function checkForCurrentConfigDoc(es, done) {
|
||||
if (!kibanaIndexExists) return done();
|
||||
console.log('checking if migration is necessary: not implemented');
|
||||
// callbacks should always be called async
|
||||
nextTick(done);
|
||||
}
|
||||
|
||||
function initConfig(config, done) {
|
||||
console.log('initializing config service');
|
||||
config.init().then(function () { done(); }, done);
|
||||
}
|
||||
});
|
||||
|
|
50
src/kibana/styles/_notify.less
Normal file
50
src/kibana/styles/_notify.less
Normal file
|
@ -0,0 +1,50 @@
|
|||
#fatal-splash-screen {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.toaster-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
visibility: hidden;
|
||||
width: 85%;
|
||||
|
||||
.toaster {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
-webkit-box-shadow: 3px 0px 19px 0px rgba(50, 50, 50, 0.67);
|
||||
-moz-box-shadow: 3px 0px 19px 0px rgba(50, 50, 50, 0.67);
|
||||
box-shadow: 3px 0px 19px 0px rgba(50, 50, 50, 0.67);
|
||||
padding: 0px 15px;
|
||||
margin: 0 0 10px 0;
|
||||
border: none;
|
||||
|
||||
button.btn {
|
||||
-webkit-border-radius: 0px;
|
||||
-moz-border-radius: 0px;
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
td {
|
||||
vertical-align: middle;
|
||||
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
width: 80%;
|
||||
}
|
||||
// :not(:first-child)
|
||||
text-align: right;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6842,6 +6842,21 @@ button.close {
|
|||
body {
|
||||
margin: 0px;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
.content .navbar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.content .application {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
notifications {
|
||||
z-index: 1;
|
||||
}
|
||||
.navbar-nav li a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -6998,6 +7013,48 @@ kbn-table .table .table td.field-name {
|
|||
kbn-table tr.even td {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
#fatal-splash-screen {
|
||||
margin: 15px;
|
||||
}
|
||||
.toaster-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
visibility: hidden;
|
||||
width: 85%;
|
||||
}
|
||||
.toaster-container .toaster {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.toaster-container .alert {
|
||||
-webkit-box-shadow: 3px 0px 19px 0px rgba(50, 50, 50, 0.67);
|
||||
-moz-box-shadow: 3px 0px 19px 0px rgba(50, 50, 50, 0.67);
|
||||
box-shadow: 3px 0px 19px 0px rgba(50, 50, 50, 0.67);
|
||||
padding: 0px 15px;
|
||||
margin: 0 0 10px 0;
|
||||
border: none;
|
||||
}
|
||||
.toaster-container .alert button.btn {
|
||||
-webkit-border-radius: 0px;
|
||||
-moz-border-radius: 0px;
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
.toaster-container .alert table {
|
||||
width: 100%;
|
||||
}
|
||||
.toaster-container .alert table td {
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
width: 20%;
|
||||
}
|
||||
.toaster-container .alert table td:first-child {
|
||||
text-align: left;
|
||||
width: 80%;
|
||||
}
|
||||
disc-field-chooser ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
@ -9,6 +9,24 @@ body {
|
|||
margin: 0px;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
.navbar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.application {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
notifications {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
//== Subnav
|
||||
//
|
||||
// Use for adding a subnav to your app
|
||||
|
@ -52,4 +70,5 @@ body {
|
|||
}
|
||||
|
||||
@import "./_table.less";
|
||||
@import "./_notify.less";
|
||||
@import "../apps/discover/styles/main.less";
|
||||
|
|
48
src/kibana/utils/mutable_watcher.js
Normal file
48
src/kibana/utils/mutable_watcher.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
define(function (require) {
|
||||
|
||||
/**
|
||||
* Helper to create a watcher than can be simply changed
|
||||
* @param {[type]} opts [description]
|
||||
* @param {[type]} initialFn [description]
|
||||
*/
|
||||
function MutableWatcher(opts, initialFn) {
|
||||
opts = opts || {};
|
||||
|
||||
var $scope = opts.$scope;
|
||||
if (!$scope) throw new TypeError('you must specify a scope.');
|
||||
|
||||
var expression = opts.expression;
|
||||
if (!expression) throw new TypeError('you must specify an expression.');
|
||||
|
||||
// the watch method to call
|
||||
var method = $scope[opts.type === 'collection' ? '$watchCollection' : '$watch'];
|
||||
|
||||
// stores the unwatch function
|
||||
var unwatcher;
|
||||
|
||||
// change the function that the watcher triggers
|
||||
function watch(watcher) {
|
||||
if (typeof unwatcher === 'function') {
|
||||
unwatcher();
|
||||
unwatcher = null;
|
||||
}
|
||||
|
||||
if (!watcher) return;
|
||||
|
||||
// include the expression as the first argument
|
||||
var args = [].slice.apply(arguments);
|
||||
args.unshift(expression);
|
||||
|
||||
// register a new unwatcher
|
||||
unwatcher = method.apply($scope, args);
|
||||
}
|
||||
|
||||
watch(initialFn);
|
||||
|
||||
// public API
|
||||
this.set = watch;
|
||||
}
|
||||
|
||||
return MutableWatcher;
|
||||
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue