Added pulldowns for query and filter. Added filter notifications, moved timepicker to navbar, reimplemented auto refresh

This commit is contained in:
Rashid Khan 2013-10-08 18:38:08 +02:00
parent d5bc550bb1
commit e54c868e7f
22 changed files with 442 additions and 307 deletions

View File

@ -295,7 +295,6 @@ function($, _, moment) {
} else if (c === '-') {
type = 2;
} else {
console.log("no type");
return false;
}
@ -311,7 +310,6 @@ function($, _, moment) {
if (type === 0) {
// rounding is only allowed on whole numbers
if (num !== 1) {
console.log("nbad rounding");
return false;
}
}
@ -373,7 +371,6 @@ function($, _, moment) {
}
break;
default:
console.log("unknown unit");
return false;
}
}

View File

@ -1,5 +1,6 @@
define([
'./dash',
'./dashLoader',
'./row'
'./row',
'./pulldown'
], function () {});

View File

@ -11,13 +11,20 @@ function (angular, _) {
$scope.loader = dashboard.current.loader;
$scope.init = function() {
$scope.advancedLoad = false;
$scope.advancedSave = false;
$scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
$scope.gist = $scope.gist || {};
$scope.elasticsearch = $scope.elasticsearch || {};
};
$scope.showDropdown = function(type) {
var _l = $scope.loader;
if(_.isUndefined(dashboard.current.loader)) {
return true;
}
var _l = dashboard.current.loader;
if(type === 'load') {
return (_l.load_elasticsearch || _l.load_gist || _l.load_local);
}

44
src/app/controllers/pulldown.js Executable file
View File

@ -0,0 +1,44 @@
define([
'angular',
'app',
'underscore'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
module.controller('PulldownCtrl', function($scope, $rootScope, $timeout,ejsResource, querySrv) {
var _d = {
collapse: false,
notice: false,
};
_.defaults($scope.pulldown,_d);
$scope.init = function() {
$scope.querySrv = querySrv;
// Provide a combined skeleton for panels that must interact with panel and row.
// This might create name spacing issues.
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
};
$scope.toggle_pulldown = function(pulldown) {
pulldown.collapse = pulldown.collapse ? false : true;
if (!pulldown.collapse) {
$timeout(function() {
$scope.$broadcast('render');
});
} else {
$scope.row.notice = false;
}
};
$scope.init();
}
);
});

View File

@ -13,7 +13,9 @@
"query": "*",
"alias": "",
"color": "#7EB26D",
"id": 0
"id": 0,
"pin": false,
"type": "lucene"
}
},
"ids": [
@ -22,24 +24,12 @@
},
"filter": {
"idQueue": [
0,
1,
2
],
"list": {
"0": {
"from": "2013-07-27T22:08:06.800Z",
"to": "2013-07-27T23:08:06.801Z",
"field": "@timestamp",
"type": "time",
"mandate": "must",
"active": true,
"alias": "",
"id": 0
}
},
"ids": [
0
]
"list": {},
"ids": []
}
},
"rows": [
@ -77,7 +67,8 @@
"style": {},
"status": "Stable"
}
]
],
"notice": false
}
],
"editable": false,
@ -85,5 +76,73 @@
"interval": "none",
"pattern": "[logstash-]YYYY.MM.DD",
"default": "_all"
}
}
},
"style": "dark",
"failover": false,
"panel_hints": true,
"pulldowns": [
{
"type": "query",
"collapse": true,
"notice": false,
"query": "*",
"pinned": true,
"history": [],
"remember": 10
},
{
"type": "filtering",
"collapse": true,
"notice": false
}
],
"nav": [
{
"type": "timepicker2",
"collapse": false,
"notice": false,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"timefield": "@timestamp",
"now": true,
"filter_id": 0
}
],
"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": true,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": true,
"hide": false
},
"refresh": false
}

View File

@ -1,4 +1,4 @@
/* global _, kbn */
/* global _ */
/*
* Complex scripted Logstash dashboard
@ -29,7 +29,7 @@ var dashboard, queries, _d_timespan;
var ARGS;
// Set a default timespan if one isn't specified
_d_timespan = '1h';
_d_timespan = '1d';
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
@ -85,12 +85,11 @@ dashboard.services.query = {
};
// Lets also add a default time filter, the value of which can be specified by the user
// This isn't strictly needed, but it gets rid of the info alert about the missing time filter
dashboard.services.filter = {
list: {
0: {
from: kbn.time_ago(ARGS.from||_d_timespan),
to: new Date(),
from: "now-"+(ARGS.from||_d_timespan),
to: "now",
field: ARGS.timefield||"@timestamp",
type: "time",
active: true,
@ -102,19 +101,6 @@ dashboard.services.filter = {
// Ok, lets make some rows. The Filters row is collapsed by default
dashboard.rows = [
{
title: "Time span",
height: "30px"
},
{
title: "Query",
height: "30px"
},
{
title: "Filters",
height: "100px",
collapse: true
},
{
title: "Chart",
height: "300px"
@ -125,37 +111,8 @@ dashboard.rows = [
}
];
// Setup some panels. A query panel and a filter panel on the same row
dashboard.rows[0].panels = [
{
title: "Set time filter",
type: 'timepicker',
span: 6,
timespan: ARGS.from||_d_timespan
}
];
// Add a filtering panel to the 3rd row
dashboard.rows[1].panels = [
{
title: 'search',
type: 'query',
span: 12
}
];
// Add a filtering panel to the 3rd row
dashboard.rows[2].panels = [
{
title: 'filters (applied globally)',
type: 'filtering',
span: 12
}
];
// And a histogram that allows the user to specify the interval and time field
dashboard.rows[3].panels = [
dashboard.rows[0].panels = [
{
title: 'events over time',
type: 'histogram',
@ -166,7 +123,7 @@ dashboard.rows[3].panels = [
];
// And a table row where you can specify field and sort order
dashboard.rows[4].panels = [
dashboard.rows[1].panels = [
{
title: 'all events',
type: 'table',

View File

@ -10,7 +10,9 @@
"query": "{{ARGS.query || '*'}}",
"alias": "",
"color": "#7EB26D",
"id": 0
"id": 0,
"pin": false,
"type": "lucene"
}
},
"ids": [
@ -23,10 +25,10 @@
],
"list": {
"0": {
"from": "2013-07-30T18:58:13.977Z",
"to": "2013-07-30T19:58:13.977Z",
"field": "@timestamp",
"type": "time",
"field": "@timestamp",
"from": "now-{{ARGS.from || '24h'}}",
"to": "now",
"mandate": "must",
"active": true,
"alias": "",
@ -39,90 +41,6 @@
}
},
"rows": [
{
"title": "Options",
"height": "50px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"title": "Set time span",
"error": "",
"span": 6,
"editable": true,
"group": [
"default"
],
"type": "timepicker",
"mode": "relative",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"timespan": "{{ARGS.from || '1h'}}",
"timefield": "@timestamp",
"timeformat": "",
"refresh": {
"enable": false,
"interval": 30,
"min": 3
},
"filter_id": 0,
"status": "Stable"
}
]
},
{
"title": "Query",
"height": "50px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"title": "Search",
"error": false,
"span": 12,
"editable": true,
"group": [
"default"
],
"type": "query",
"label": "Search",
"history": [],
"remember": 10,
"pinned": true,
"query": "*"
}
]
},
{
"title": "Filters",
"height": "50px",
"editable": true,
"collapse": true,
"collapsable": true,
"panels": [
{
"title": "dashboard filters",
"error": false,
"span": 12,
"editable": true,
"group": [
"default"
],
"type": "filtering"
}
]
},
{
"title": "Graph",
"height": "350px",
@ -142,7 +60,7 @@
"value_field": null,
"auto_int": true,
"resolution": 100,
"interval": "30s",
"interval": "10m",
"fill": 3,
"linewidth": 3,
"timezone": "browser",
@ -163,9 +81,30 @@
0
]
},
"title": "Events over time"
"title": "Events over time",
"intervals": [
"auto",
"1s",
"1m",
"5m",
"10m",
"30m",
"1h",
"3h",
"12h",
"1d",
"1w",
"1M",
"1y"
],
"options": true,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": false
}
}
]
],
"notice": false
},
{
"title": "Events",
@ -207,9 +146,12 @@
]
},
"field_list": true,
"status": "Stable"
"status": "Stable",
"trimFactor": 300,
"normTimes": true
}
]
],
"notice": false
}
],
"editable": true,
@ -218,5 +160,72 @@
"interval": "day",
"pattern": "[logstash-]YYYY.MM.DD",
"default": "NO_TIME_FILTER_OR_INDEX_PATTERN_NOT_MATCHED"
}
},
"style": "dark",
"panel_hints": true,
"pulldowns": [
{
"type": "query",
"collapse": false,
"notice": false,
"query": "*",
"pinned": true,
"history": [],
"remember": 10
},
{
"type": "filtering",
"collapse": true,
"notice": false
}
],
"nav": [
{
"type": "timepicker2",
"collapse": false,
"notice": false,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"timefield": "@timestamp",
"now": true,
"filter_id": 0
}
],
"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": true,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": true,
"hide": false
},
"refresh": false
}

View File

@ -10,6 +10,7 @@ function (angular) {
return {
restrict: 'E',
link: function($scope, elem, attr) {
// once we have the template, scan it for controllers and
// load the module.js if we have any

View File

@ -1,7 +1,7 @@
<div class="row-fluid">
<div class="span2">
<label class="small">Mode</label>
<select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total']"></select>
<select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total','change']"></select>
</div>
<div class="span2">
<label class="small">Time Field</label>

View File

@ -50,30 +50,32 @@
<span ng-show="panel.legend" class="small"><span ng-show="panel.value_field && panel.mode != 'count'">{{panel.value_field}}</span> {{panel.mode}} per <strong>{{panel.interval}}</strong> | (<strong>{{hits}}</strong> hits)</span>
</div>
<form class="form-inline bordered histogram-options" ng-show="options">
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
Bars
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
Lines
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
Points
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
Stack
</label>
</div>
<span>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
Bars
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
Lines
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
Points
</label>
</div>
<div class="checkbox">
<label class="small">
<input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
Stack
</label>
</div>
</span>
<span ng-show="panel.stack">
<div class="checkbox">
<label style="white-space:nowrap" class="small">

View File

@ -308,8 +308,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
}
filterSrv.set({
type:'time',
from:moment.utc(_from),
to:moment.utc(_to),
from:moment.utc(_from).toDate(),
to:moment.utc(_to).toDate(),
field:$scope.panel.time_field
});
};

View File

@ -49,7 +49,7 @@
</th>
</thead>
<tbody ng-repeat="event in data | slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'">
<tbody ng-repeat="event in data| slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'">
<tr ng-click="toggle_details(event)" class="pointer">
<td ng-show="panel.fields.length<1">{{event._source|stringify|tableTruncate:panel.trimFactor:1}}</td>
<td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields" ng-bind-html-unsafe="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"></td>
@ -69,7 +69,7 @@
<th>Action</th>
<th>Value</th>
</thead>
<tr ng-repeat="(key,value) in event.kibana._source" ng-class-odd="'odd'">
<tr ng-repeat="(key,value) in event.kibana._source track by $index" ng-class-odd="'odd'">
<td>{{key}}</td>
<td style="white-space:nowrap">
<i class='icon-search pointer' ng-click="build_search(key,value)" bs-tooltip="'Add filter to match this value'"></i>

View File

@ -39,7 +39,7 @@
<div class="timepicker-from-column">
<label class="small">From</label>
<div class="fake-input timepicker-input">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="mm/dd/yyyy" required bs-datepicker />@
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
@ -53,7 +53,7 @@
<div class="fake-input timepicker-input">
<div ng-hide="panel.now">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="mm/dd/yyyy" required bs-datepicker />@
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.second" required ng-pattern="patterns.second" onClick="this.select();"/>.

View File

@ -1,9 +1,13 @@
<div class="row-fluid">
<div class="span6">
<div class="span4">
<label class="small">Relative time options <small>comma seperated</small></label>
<input type="text" array-join class="input-large" ng-model="panel.time_options">
</div>
<div class="span3">
<div class="span4">
<label class="small">Auto-refresh options <small>comma seperated</small></label>
<input type="text" array-join class="input-large" ng-model="panel.refresh_intervals">
</div>
<div class="span2">
<label class="small">Time Field</label>
<input type="text" class="input-small" ng-model="panel.timefield">
</div>

View File

@ -14,9 +14,10 @@
<form name="input" style="margin:3px 0 0 0">
<ul class="nav nav-pills timepicker-dropdown">
<li class="dropdown" bs-tooltip="(time.from.date | date:'yyyy-MM-dd HH:mm:ss.sss') + ' to ' +(time.to.date | date:'yyyy-MM-dd HH:mm:ss.sss')" data-placement="bottom" ng-click="dismiss();">
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="#">
<span class="small" ng-show="filterSrv.idsByType('time').length">
<span ng-show="filterSrv.idsByType('time').length">
<span class="pointer" ng-hide="panel.now">{{time.from.date | date:'MMM d, y HH:mm:ss'}}</span>
<span class="pointer" ng-show="panel.now">{{time.from.date | moment:'ago'}}</span>
to
@ -24,14 +25,27 @@
<span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
</span>
<span ng-hide="filterSrv.idsByType('time').length">Set a time filter</span>
<i class="small icon-caret-down"></i>
<span ng-show="dashboard.current.refresh" class="text-warning">refreshed every {{dashboard.current.refresh}} </span>
<i class="icon-caret-down"></i>
</a>
<ul class="dropdown-menu">
<li ng-repeat='timespan in panel.time_options'>
<!-- Relative time options -->
<li ng-repeat='timespan in panel.time_options track by $index'>
<a ng-click="setRelativeFilter(timespan)">Last {{timespan}}</a>
</li>
<!-- Auto refresh submenu -->
<li class="dropdown-submenu">
<a href="#">Auto-Refresh</a>
<ul class="dropdown-menu">
<li><a ng-click="dashboard.set_interval(false)">Off</a></li>
<li ng-repeat="interval in panel.refresh_intervals track by $index"><a ng-click="dashboard.set_interval(interval)">Every {{interval}}</a></li>
</ul>
</li>
<li><a ng-click="customTime()">Custom</a></li>
</ul>
</li>
</ul>

View File

@ -36,16 +36,10 @@ function (angular, app, _, moment, kbn) {
// Set and populate defaults
var _d = {
status : "Stable",
mode : "relative",
time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'],
timespan : '15m',
timefield : '@timestamp',
timeformat : "",
refresh : {
enable : false,
interval: 30,
min : 3
}
refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'],
timefield : '@timestamp'
};
_.defaults($scope.panel,_d);
@ -212,9 +206,7 @@ function (angular, app, _, moment, kbn) {
// Do not use the results of this function unless you plan to use setHour/Minutes/etc on the result
var datepickerToLocal = function(date) {
console.log(date);
date = moment(date).clone().toDate();
console.log(moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate());
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate();
};

View File

@ -1,38 +1,23 @@
<li ng-controller="RowCtrl"><kibana-simple-panel type="'timepicker2'" ng-cloak></kibana-simple-panel></li>
<style>
.noarrow>a:after {
display: none !important;
}
</style>
<li ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl"><kibana-simple-panel type="pulldown.type" panel="pulldown" ng-cloak></kibana-simple-panel></li>
<li><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
<li class="dropdown" bs-tooltip="'Load'" data-placement="bottom" ng-show="showDropdown('load')" >
<a href="#" class="dropdown-toggle" data-toggle="dropdown" ng-click="elasticsearch_dblist('title:'+elasticsearch.query+'*')">
<i class='icon-folder-open'></i>
</a>
<ul class="dropdown-menu" style="padding:10px">
<li ng-show='loader.load_local'>
<h5>Local File <tip>Load dashboard JSON layout from file</tip></h5>
<form>
<input type="file" id="dashupload" dash-upload /><br>
</form>
</li>
<li ng-show='loader.load_gist'>
<h5>Gist <tip>Enter a gist number or url</tip></h5>
<form>
<input type="text" ng-model="gist.url"/ placeholder="Gist number or URL"><br>
<button class="btn" ng-click="gist_dblist(dashboard.gist_id(gist.url))" ng-show="dashboard.is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
<h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
<h6 ng-hide="gist.files.length">No gist dashboards found</h6>
<table class="table table-condensed table-striped">
<tr ng-repeat="file in gist.files">
<td><a ng-click="dashboard.dash_load(file)">{{file.title}}</a></td>
</tr>
</table>
</form>
</li>
<li ng-show='loader.load_elasticsearch'>
<h5>Elasticsearch</h5>
<form>
<li ng-show='dashboard.current.loader.load_elasticsearch'>
<form class="nomargin">
<input type="text" ng-model="elasticsearch.query" ng-change="elasticsearch_dblist('title:'+elasticsearch.query+'*')" placeholder="Type to filter"/>
</form>
<h6 ng-show="elasticsearch.dashboards.length">Elasticsearch stored dashboards</h6>
<h6 ng-hide="elasticsearch.dashboards.length">No dashboards matching your query found</h6>
<table class="table table-condensed table-striped">
<tr ng-repeat="row in elasticsearch.dashboards | orderBy:['_id']">
@ -42,38 +27,81 @@
</tr>
</table>
</li>
<li class="dropdown-submenu noarrow">
<a tabindex="-1" href="#" class="small" style="padding:0"><i class="icon-caret-left"></i> Advanced</a>
<ul class="dropdown-menu" style="padding:10px">
<li ng-show='dashboard.current.loader.load_local'>
<h5>Local File <tip>Load dashboard JSON layout from file</tip></h5>
<form>
<input type="file" id="dashupload" dash-upload /><br>
</form>
</li>
<li ng-show='dashboard.current.loader.load_gist'>
<h5>Gist <tip>Enter a gist number or url</tip></h5>
<form>
<input type="text" ng-model="gist.url"/ placeholder="Gist number or URL"><br>
<button class="btn" ng-click="gist_dblist(dashboard.gist_id(gist.url))" ng-show="dashboard.is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
<h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
<h6 ng-hide="gist.files.length || !gist.url.length">No gist dashboards found</h6>
<table class="table table-condensed table-striped">
<tr ng-repeat="file in gist.files">
<td><a ng-click="dashboard.dash_load(file)">{{file.title}}</a></td>
</tr>
</table>
</form>
</li>
</ul>
</li>
</ul>
</li>
<li class="dropdown" bs-tooltip="'Save'" data-placement="bottom" ng-show="showDropdown('save')">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class='icon-save'></i>
</a>
<ul class="dropdown-menu" style="padding:10px">
<li ng-show="loader.save_default || loader.save_local">
<h5>Locally</h5>
<ul class="unstyled">
<li><a class="link" ng-show="loader.save_local" ng-click="dashboard.to_file()"><i class="icon-download"></i> Export to File</a> <tip>Export layout, not data, to file</tip></li>
<li><a class="link" ng-show="loader.save_default" ng-click="set_default()"><i class="icon-bookmark"></i> Set as Browser Default</a> <tip>Store dashboard preference to browser's localStorage</tip></li>
<li><a class="link" ng-show="loader.save_default" ng-click="purge_default()"><i class="icon-ban-circle"></i> Clear Browser Default</a></li>
</ul>
</li>
<li ng-show="loader.save_gist">
<h5>Gist</h5>
<form class="input-append">
<input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
<button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
</form><br>
<small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
</li>
<li ng-show="loader.save_elasticsearch">
<h5>Elasticsearch</h5>
<form class="input-append">
<input class='input-medium' placeholder="{{dashboard.current.title}}" type="text" ng-model="elasticsearch.title"/>
<li ng-show="dashboard.current.loader.save_elasticsearch">
<form class="input-append nomargin">
<input class='input-medium' ng-model="dashboard.current.title" type="text" ng-model="elasticsearch.title"/>
<button class="btn" ng-click="elasticsearch_save('dashboard')"><i class="icon-save"></i></button>
</form>
</li>
</ul>
</li>
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',loader.save_temp_ttl)" bs-modal="'app/partials/dashLoaderShare.html'"><i class='icon-share'></i></a></li>
<li ng-show="dashboard.current.editable "bs-tooltip="'Configure dashboard'" data-placement="bottom"><a href='#' bs-modal="'app/partials/dasheditor.html'"><i class='icon-cog pointer'></i></a></li>
<li class="dropdown-submenu noarrow">
<a tabindex="-1" href="#" class="small" style="padding:0"><i class="icon-caret-left"></i> Advanced</a>
<ul class="dropdown-menu">
<li ng-show="dashboard.current.loader.save_default">
<a class="link" ng-click="set_default()">Set as my home</a>
</li>
<li ng-show="dashboard.current.loader.save_default">
<a class="link" ng-click="purge_default()">Clear my home</a>
</li>
<li ng-show="dashboard.current.loader.save_local">
<a class="link" ng-click="dashboard.to_file()">Export schema</a>
</li>
<li ng-show="dashboard.current.loader.save_gist" style="margin:10px">
<h6>Gist</h6>
<form class="input-append">
<input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
<button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
</form><br>
<small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
</li>
</ul>
</li>
</ul>
</li>
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',dashboard.current.loader.save_temp_ttl)" bs-modal="'app/partials/dashLoaderShare.html'"><i class='icon-share'></i></a></li>
<li ng-show="dashboard.current.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a href='#' bs-modal="'app/partials/dasheditor.html'"><i class='icon-cog pointer'></i></a></li>

View File

@ -1,12 +1,12 @@
<!-- is there a better way to repeat without actually affecting the page? -->
<nil ng-repeat="row in dashboard.current.pulldowns" ng-controller="RowCtrl">
<div class="top-row-open" ng-hide="row.collapse">
<kibana-simple-panel type="row.type" ng-cloak></kibana-simple-panel>
<nil ng-repeat="pulldown in dashboard.current.pulldowns" ng-controller="PulldownCtrl">
<div class="top-row-open" ng-hide="pulldown.collapse">
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
</div>
<div class="top-row-close pointer" ng-click="toggle_row(row);dismiss();" bs-tooltip="'Toggle '+row.type" data-placement="bottom">
<span class="small row-text">{{row.type}}</span>
<i class="small" ng-class="{'icon-expand':row.collapse,'icon-collapse-top':!row.collapse}"></i>
<i class="small icon-circle text-warning" ng-show="row.notice && row.collapse"></i>
<div class="top-row-close pointer" ng-click="toggle_pulldown(pulldown);dismiss();" bs-tooltip="'Toggle '+pulldown.type" data-placement="bottom">
<span class="small row-text">{{pulldown.type}}</span>
<i class="small" ng-class="{'icon-expand':pulldown.collapse,'icon-collapse-top':!pulldown.collapse}"></i>
<i class="small icon-star text-warning" ng-show="row.notice && pulldown.collapse"></i>
</div>
</nil>

View File

@ -1,9 +1,11 @@
<div class="modal-body">
<div class="pull-right editor-title">Dashboard settings</div>
<div ng-model="editor.index" bs-tabs>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['General','Index','Rows','Controls']" data-title="{{tab}}">
</div>
<div ng-repeat="tab in dashboard.current.nav" data-title="{{tab.type}}">
</div>
</div>
<div ng-show="editor.index == 0">
@ -103,46 +105,49 @@
<h5>Allow saving to</h5>
<div class="row-fluid">
<div class="span2">
<label class="small">File</label><input type="checkbox" ng-model="loader.save_local" ng-checked="loader.save_local">
<label class="small">File</label><input type="checkbox" ng-model="dashboard.current.loader.save_local" ng-checked="dashboard.current.loader.save_local">
</div>
<div class="span2">
<label class="small">Browser</label><input type="checkbox" ng-model="loader.save_default" ng-checked="loader.save_default">
<label class="small">Browser</label><input type="checkbox" ng-model="dashboard.current.loader.save_default" ng-checked="dashboard.current.loader.save_default">
</div>
<div class="span2">
<label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github<tip></label><input type="checkbox" ng-model="loader.save_gist" ng-checked="loader.save_gist">
<label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github<tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_gist" ng-checked="dashboard.current.loader.save_gist">
</div>
<div class="span2">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="loader.save_elasticsearch" ng-checked="loader.save_elasticsearch">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.save_elasticsearch" ng-checked="dashboard.current.loader.save_elasticsearch">
</div>
</div>
<h5>Allow loading from</h5>
<div class="row-fluid">
<div class="span2">
<label class="small">Local file</label><input type="checkbox" ng-model="loader.load_local" ng-checked="loader.load_local">
<label class="small">Local file</label><input type="checkbox" ng-model="dashboard.current.loader.load_local" ng-checked="dashboard.current.loader.load_local">
</div>
<div class="span2">
<label class="small">Gist</label><input type="checkbox" ng-model="loader.load_gist" ng-checked="loader.load_gist">
<label class="small">Gist</label><input type="checkbox" ng-model="dashboard.current.loader.load_gist" ng-checked="dashboard.current.loader.load_gist">
</div>
<div class="span2">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="loader.load_elasticsearch" ng-checked="loader.load_elasticsearch">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.load_elasticsearch" ng-checked="dashboard.current.loader.load_elasticsearch">
</div>
<div class="span3" ng-show="loader.load.elasticsearch">
<label class="small">ES list size</label><input class="input-mini" type="number" ng-model="loader.load_elasticsearch_size">
<div class="span3" ng-show="dashboard.current.loader.load.elasticsearch">
<label class="small">ES list size</label><input class="input-mini" type="number" ng-model="dashboard.current.loader.load_elasticsearch_size">
</div>
</div>
<h5>Sharing</h5>
<div class="row-fluid">
<div class="span2" >
<label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="loader.save_temp" ng-checked="loader.save_temp">
<label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp" ng-checked="dashboard.current.loader.save_temp">
</div>
<div class="span2" ng-show="loader.save_temp">
<label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="loader.save_temp_ttl_enable">
<div class="span2" ng-show="dashboard.current.loader.save_temp">
<label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp_ttl_enable">
</div>
<div class="span5" ng-show="loader.save_temp &amp;&amp; loader.save_temp_ttl_enable">
<label class="small">TTL Duration <tip>Elasticsearch date math, eg: 1m,1d,1w,30d </tip></label><input class="input-small" type="text" ng-model="loader.save_temp_ttl">
<div class="span5" ng-show="dashboard.current.loader.save_temp &amp;&amp; dashboard.current.loader.save_temp_ttl_enable">
<label class="small">TTL Duration <tip>Elasticsearch date math, eg: 1m,1d,1w,30d </tip></label><input class="input-small" type="text" ng-model="dashboard.current.loader.save_temp_ttl">
</div>
</div>
</div>
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl" ng-include="'./app/panels/'+pulldown.type+'/editor.html'" ng-show="editor.index == 4+$index">
</div>
</div>
<div class="modal-footer">

View File

@ -13,7 +13,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
var module = angular.module('kibana.services');
module.service('dashboard', function($routeParams, $http, $rootScope, $injector, $location,
module.service('dashboard', function(
$routeParams, $http, $rootScope, $injector, $location, $timeout,
ejsResource, timer, kbnIndex, alertSrv
) {
// A hash of defaults to use when loading a dashboard
@ -33,6 +34,11 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
type: 'filtering'
}
],
nav: [
{
type: 'timepicker2'
}
],
services: {},
loader: {
save_gist: false,
@ -42,10 +48,10 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
save_temp: true,
save_temp_ttl_enable: true,
save_temp_ttl: '30d',
load_gist: true,
load_gist: false,
load_elasticsearch: true,
load_elasticsearch_size: 20,
load_local: true,
load_local: false,
hide: false
},
index: {
@ -53,6 +59,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
pattern: '_all',
default: 'INDEX_MISSING'
},
refresh: false
};
// An elasticJS client to use
@ -172,6 +179,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
self.indices = [dashboard.index.default];
}
// Set the current dashboard
self.current = _.clone(dashboard);
// Ok, now that we've setup the current dashboard, we can inject our services
@ -182,11 +190,16 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
querySrv.init();
filterSrv.init();
// If there's an index interval set and no existing time filter, send a refresh to set one
// If there's an interval set, the indices have not been calculated yet,
// so there is no data. Call refresh to calculate the indices and notify the panels.
if(dashboard.index.interval !== 'none') {
self.refresh();
}
if(dashboard.refresh) {
self.set_interval(dashboard.refresh);
}
return true;
};
@ -420,6 +433,23 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
return false;
});
};
this.set_interval = function (interval) {
self.current.refresh = interval;
if(interval) {
var _i = kbn.interval_to_ms(interval);
timer.cancel(self.refresh_timer);
self.refresh_timer = timer.register($timeout(function() {
self.set_interval(interval);
self.refresh();
},_i));
self.refresh();
} else {
timer.cancel(self.refresh_timer);
}
};
});
});

View File

@ -7,26 +7,20 @@ function (angular, _) {
var module = angular.module('kibana.services');
module.service('panelMove', function(dashboard, $rootScope, alertSrv) {
module.service('panelMove', function(dashboard, $rootScope) {
/* each of these can take event,ui,data parameters */
var notices = [];
this.onStart = function() {
dashboard.panelDragging = true;
notices.push(alertSrv.set('Moving','Drop this panel into an available space, or on top of another panel','info'));
$rootScope.$apply();
};
this.onOver = function() {
notices.push(alertSrv.set('Add panel',
'Drop to add panel to this row. Panel will use row height, but retain their span','success'));
$rootScope.$apply();
};
this.onOut = function() {
clearNotices({severity:'success'});
$rootScope.$apply();
};
@ -63,21 +57,12 @@ function (angular, _) {
};
var cleanup = function () {
_.each(notices, function(n){
alertSrv.clear(n);
});
_.each(dashboard.current.rows, function(row) {
row.panels = _.without(row.panels,{});
row.panels = _.compact(row.panels);
});
};
var clearNotices = function(options) {
_.each(_.where(notices,options), function(n) {
alertSrv.clear(n);
});
};
});
});

View File

@ -26,7 +26,7 @@
<link rel="stylesheet" href="css/bootstrap-responsive.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last" style="position:fixed">
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last">
<button type="button" class="close" ng-click="dashAlerts.clear(alert)" style="padding-right:50px">&times;</button>
<strong>{{alert.title}}</strong> <span ng-bind-html-unsafe='alert.text'></span> <div style="padding-right:10px" class='pull-right small'> {{$index + 1}} alert(s) </div>
</div>