define([ 'angular', 'jquery', 'kbn', 'underscore', 'config', 'moment', 'modernizr', 'filesaver' ], function (angular, $, kbn, _, config, moment, Modernizr) { 'use strict'; var module = angular.module('kibana.services'); module.service('dashboard', function( $routeParams, $http, $rootScope, $injector, $location, $timeout, ejsResource, timer, kbnIndex, alertSrv ) { // A hash of defaults to use when loading a dashboard var _dash = { title: "", style: "dark", editable: true, failover: false, panel_hints: true, rows: [], pulldowns: [ { type: 'query', }, { type: 'filtering' } ], nav: [ { type: 'timepicker' } ], services: {}, loader: { save_gist: false, save_elasticsearch: true, save_local: true, save_default: true, save_temp: true, save_temp_ttl_enable: true, save_temp_ttl: '30d', load_gist: false, load_elasticsearch: true, load_elasticsearch_size: 20, load_local: false, hide: false }, index: { interval: 'none', pattern: '_all', default: 'INDEX_MISSING', warm_fields: true }, refresh: false }; // An elasticJS client to use var ejs = ejsResource(config.elasticsearch); var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/; // Store a reference to this var self = this; var filterSrv,querySrv; this.current = _.clone(_dash); this.last = {}; this.availablePanels = []; $rootScope.$on('$routeChangeSuccess',function(){ // Clear the current dashboard to prevent reloading self.current = {}; self.indices = []; route(); }); var route = function() { // Is there a dashboard type and id in the URL? if(!(_.isUndefined($routeParams.kbnType)) && !(_.isUndefined($routeParams.kbnId))) { var _type = $routeParams.kbnType; var _id = $routeParams.kbnId; switch(_type) { case ('elasticsearch'): self.elasticsearch_load('dashboard',_id); break; case ('temp'): self.elasticsearch_load('temp',_id); break; case ('file'): self.file_load(_id); break; case('script'): self.script_load(_id); break; case('local'): self.local_load(); break; default: $location.path(config.default_route); } // No dashboard in the URL } else { // Check if browser supports localstorage, and if there's an old dashboard. If there is, // inform the user that they should save their dashboard to Elasticsearch and then set that // as their default if (Modernizr.localstorage) { if(!(_.isUndefined(window.localStorage['dashboard'])) && window.localStorage['dashboard'] !== '') { console.log(window.localStorage['dashboard']); $location.path(config.default_route); alertSrv.set('Saving to browser storage has been replaced',' with saving to Elasticsearch.'+ ' Click here to load your old dashboard anyway.'); } else if(!(_.isUndefined(window.localStorage.kibanaDashboardDefault))) { $location.path(window.localStorage.kibanaDashboardDefault); } else { $location.path(config.default_route); } // No? Ok, grab the default route, its all we have now } else { $location.path(config.default_route); } } }; // Since the dashboard is responsible for index computation, we can compute and assign the indices // here before telling the panels to refresh this.refresh = function() { if(self.current.index.interval !== 'none') { if(_.isUndefined(filterSrv)) { return; } if(filterSrv.idsByType('time').length > 0) { var _range = filterSrv.timeRange('last'); kbnIndex.indices(_range.from,_range.to, self.current.index.pattern,self.current.index.interval ).then(function (p) { if(p.length > 0) { self.indices = p; } else { // Option to not failover if(self.current.failover) { self.indices = [self.current.index.default]; } else { // Do not issue refresh if no indices match. This should be removed when panels // properly understand when no indices are present alertSrv.set('No results','There were no results because no indices were found that match your'+ ' selected time span','info',5000); return false; } } // Don't resolve queries until indices are updated querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');}); }); } else { if(self.current.failover) { self.indices = [self.current.index.default]; querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');}); } else { alertSrv.set("No time filter", 'Timestamped indices are configured without a failover. Waiting for time filter.', 'info',5000); } } } else { self.indices = [self.current.index.default]; querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');}); } }; var dash_defaults = function(dashboard) { _.defaults(dashboard,_dash); _.defaults(dashboard.index,_dash.index); _.defaults(dashboard.loader,_dash.loader); return dashboard; }; this.dash_load = function(dashboard) { // Cancel all timers timer.cancel_all(); // Make sure the dashboard being loaded has everything required dashboard = dash_defaults(dashboard); // If not using time based indices, use the default index if(dashboard.index.interval === 'none') { self.indices = [dashboard.index.default]; } // Set the current dashboard self.current = _.clone(dashboard); // Delay this until we're sure that querySrv and filterSrv are ready $timeout(function() { // Ok, now that we've setup the current dashboard, we can inject our services querySrv = $injector.get('querySrv'); filterSrv = $injector.get('filterSrv'); // Make sure these re-init querySrv.init(); filterSrv.init(); },0).then(function() { // Call refresh to calculate the indices and notify the panels that we're ready to roll self.refresh(); }); if(dashboard.refresh) { self.set_interval(dashboard.refresh); } // Set the available panels for the "Add Panel" drop down self.availablePanels = _.difference(config.panel_names, _.pluck(_.union(self.current.nav,self.current.pulldowns),'type')); // Take out any that we're not allowed to add from the gui. self.availablePanels = _.difference(self.availablePanels,config.hidden_panels); return true; }; this.gist_id = function(string) { if(self.is_gist(string)) { return string.match(gist_pattern)[0].replace(/.*\//, ''); } }; this.is_gist = function(string) { if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) { return string.match(gist_pattern).length > 0 ? true : false; } else { return false; } }; this.to_file = function() { var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"}); // from filesaver.js window.saveAs(blob, self.current.title+"-"+new Date().getTime()); return true; }; this.set_default = function(route) { console.log(route); if (Modernizr.localstorage) { // Purge any old dashboards if(!_.isUndefined(window.localStorage['dashboard'])) { delete window.localStorage['dashboard']; } window.localStorage.kibanaDashboardDefault = route; return true; } else { return false; } }; this.purge_default = function() { if (Modernizr.localstorage) { // Purge any old dashboards if(!_.isUndefined(window.localStorage['dashboard'])) { delete window.localStorage['dashboard']; } delete window.localStorage.kibanaDashboardDefault; return true; } else { return false; } }; // TOFIX: Pretty sure this breaks when you're on a saved dashboard already this.share_link = function(title,type,id) { return { location : window.location.href.replace(window.location.hash,""), type : type, id : id, link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+id, title : title }; }; var renderTemplate = function(json,params) { var _r; _.templateSettings = {interpolate : /\{\{(.+?)\}\}/g}; var template = _.template(json); var rendered = template({ARGS:params}); try { _r = angular.fromJson(rendered); } catch(e) { _r = false; } return _r; }; this.local_load = function() { var dashboard = JSON.parse(window.localStorage['dashboard']); dashboard.rows.unshift({ height: "30", title: "Deprecation Notice", panels: [ { title: 'WARNING: Legacy dashboard', type: 'text', span: 12, mode: 'html', content: 'This dashboard has been loaded from the browsers local cache. If you use '+ 'another brower or computer you will not be able to access it! '+ '\n\n