Commit 085b6888 by Rashid Khan

Added fields and hits panels, updated config and default dashboard.

parent 70f6a297
...@@ -3,11 +3,8 @@ ...@@ -3,11 +3,8 @@
The settings before the break are the only ones that are currently implemented The settings before the break are the only ones that are currently implemented
The remaining settings do nothing The remaining settings do nothing
timespan: Default timespan (eg 1d, 30d, 6h, 20m) elasticsearch: URL to your elasticsearch server
refresh: Milliseconds between auto refresh.
timeformat: Format for time in histograms (might go away) timeformat: Format for time in histograms (might go away)
timefield: Field to use for ISO8601 timestamps (might go away)
indexpattern: Timestamping pattern for time based indices,
modules: Panel modules to load. In the future these will be inferred modules: Panel modules to load. In the future these will be inferred
from your initial dashboard, though if you share dashboards you from your initial dashboard, though if you share dashboards you
will probably need to list them all here will probably need to list them all here
...@@ -17,22 +14,17 @@ NOTE: No timezone support yet, everything is in UTC at the moment. ...@@ -17,22 +14,17 @@ NOTE: No timezone support yet, everything is in UTC at the moment.
If you need to configure the default dashboard, please see dashboard.js If you need to configure the default dashboard, please see dashboard.js
shared.json contains an example sharable dashboard. Note the subtle differences shared.json contains an example sharable dashboard. Note the subtle differences
between dashboard.js and shared.json. Once is a javascript object, the other is between dashboard.js and shared.json. One is a javascript object, the other is
json. json.
*/ */
var config = new Settings( var config = new Settings(
{ {
timespan: '15m',
refresh: 30000,
elasticsearch: 'http://localhost:9200', elasticsearch: 'http://localhost:9200',
timeformat: 'mm/dd HH:MM:ss', timeformat: 'mm/dd HH:MM:ss',
timefield: '@timestamp',
indexpattern: '"shakespeare"',
modules: ['histogram','map','pie','table','stringquery','sort', modules: ['histogram','map','pie','table','stringquery','sort',
'timepicker','text'], 'timepicker','text','fields','hits'],
defaultfields: ['line_text'],
perpage: 50, perpage: 50,
timezone: 'user', timezone: 'user',
operator: 'OR', operator: 'OR',
......
...@@ -9,17 +9,18 @@ var dashboards = ...@@ -9,17 +9,18 @@ var dashboards =
{ {
type : "stringquery", type : "stringquery",
span : 12, span : 12,
group : ['default','counter','histogram']
} }
] ]
}, },
{ {
title: "Options", title: "Status",
collapse: true, collapse: false,
height: "30px", height: "100px",
panels: [ panels: [
{ {
type : "timepicker", type : "timepicker",
span : 5, span : 4,
mode : 'relative', mode : 'relative',
index : "\"shakespeare\"", index : "\"shakespeare\"",
refresh : { refresh : {
...@@ -33,28 +34,73 @@ var dashboards = ...@@ -33,28 +34,73 @@ var dashboards =
}, },
{ {
type : "sort", type : "sort",
span : 4, span : 3,
},
{
title : "Histogram Timer",
type : "timepicker",
span : 0,
mode : 'relative',
timespan : '5m',
index : "\"shakespeare\"",
refresh : {
enable : true,
interval: 30,
min : 10
},
timefield: '@timestamp',
group: 'histogram',
},
{
type : "histogram",
span : 3,
show : ['lines'],
fill : 0.3,
group : "histogram",
query : [
{ label : "Event Rate", query : "*", color: '#FF7400' }
],
},
{
title : "Counter Timer",
type : "timepicker",
span : 0,
mode : 'relative',
timespan : '30d',
index : "\"shakespeare\"",
refresh : {
enable : true,
interval: 30,
min : 10
},
timefield: '@timestamp',
group: 'counter',
},
{
type : "hits",
title : "Lines Completed",
span : 2,
group : 'counter',
}, },
{ {
type : "text", type : "text",
fontsize : "85%", style : {"font-size":"85%"},
span: 3, span: 0,
content : "Panels can send events to other panels. In the case of" + content : "Rows are collapsable, and input panels can send event to" +
" the sort panel, it also receives a field event that it uses" + " multiple groups. The Search panel is part of one group, while" +
" to populate its list of fields from the table panel. The time " + " the time panel is part of two"
" selector is a member of two groups."
}, },
] ]
}, },
{ {
title: "Top 3 Characters", title: "Top 3 Characters",
collapse: false, collapse: true,
height: "160px", height: "150px",
panels: [ panels: [
{ {
type : "text", type : "text",
title : "About", title : "About",
fontsize : "85%", style : {"font-size":"85%"},
span: 2, span: 2,
content : "These donut charts demonstrate configurable binding." + content : "These donut charts demonstrate configurable binding." +
" They exist in a different group from the other panels and are" + " They exist in a different group from the other panels and are" +
...@@ -136,14 +182,15 @@ var dashboards = ...@@ -136,14 +182,15 @@ var dashboards =
}, },
{ {
title: "Lines of Plays", title: "Lines of Plays",
height: "250px", height: "210px",
collapse: true, collapse: false,
panels: [ panels: [
{ {
title : "Plays", title : "Plays",
type : "pie", type : "pie",
span : 4, span : 4,
size : 8, size : 8,
labels : false,
colors : ['#BF3030','#1D7373','#86B32D','#A60000','#006363','#679B00'], colors : ['#BF3030','#1D7373','#86B32D','#A60000','#006363','#679B00'],
field : 'country', field : 'country',
//query : { query: "*", field: "country"} //query : { query: "*", field: "country"}
...@@ -152,10 +199,10 @@ var dashboards = ...@@ -152,10 +199,10 @@ var dashboards =
{ {
type : "text", type : "text",
title : "About", title : "About",
fontsize : "85%", style : {"font-size":"85%"},
span: 2, span: 0,
content : "The table panel can be sorted via a sort panel, or by" + content : "The table panel can be sorted via a sort panel, or by" +
" clicking the table header. Unlike the pie charts above, this" + " clicking the table header. Unlike the donut charts above, this" +
" pie is bound to the query input. Try searching for a speaker" + " pie is bound to the query input. Try searching for a speaker" +
" (eg, FALSTAFF) to see a break down of the plays they appear in.", " (eg, FALSTAFF) to see a break down of the plays they appear in.",
}, },
...@@ -164,14 +211,20 @@ var dashboards = ...@@ -164,14 +211,20 @@ var dashboards =
type : "table", type : "table",
span : 6, span : 6,
query : "*", query : "*",
style : {"font-size":"85%"},
fields : ['@timestamp','play_name','speaker','text_entry'], fields : ['@timestamp','play_name','speaker','text_entry'],
} },
{
type : "fields",
title : "Fields",
span : 2,
},
] ]
}, },
{ {
title: "Monkey Monitoring", title: "Monkey Monitoring",
collapse: false, collapse: false,
height: "275px", height: "225px",
panels: [ panels: [
{ {
title : "Monkey Shakespeare Lines", title : "Monkey Shakespeare Lines",
...@@ -197,7 +250,7 @@ var dashboards = ...@@ -197,7 +250,7 @@ var dashboards =
{ {
type : "text", type : "text",
title : "About", title : "About",
fontsize : "85%", style : {"font-size":"85%"},
span: 2, span: 2,
content : "Histograms can show multiple queries. In the case that a" + content : "Histograms can show multiple queries. In the case that a" +
" multi-query histogram is bound to a query input, only the first" + " multi-query histogram is bound to a query input, only the first" +
......
...@@ -3,97 +3,17 @@ ...@@ -3,97 +3,17 @@
'use strict'; 'use strict';
angular.module('kibana.controllers', []) angular.module('kibana.controllers', [])
.controller('DashCtrl', function($scope, $location, $http, $timeout, ejsResource) { .controller('DashCtrl', function($scope, ejsResource) {
$scope.config = config; $scope.config = config;
$scope.dashboards = dashboards $scope.dashboards = dashboards
/*
$scope.timespan = config.timespan
$scope.time = {
from : time_ago($scope.timespan),
to : new Date()
}
// I'm leaving in all this refresh stuff until I figure out how index
// list caching should work. Maybe it should be handled by each time panel?
// That would require dashboard to contain a time panel. Hmm.
$scope.counter = 0;
$scope.playing = true;
$scope.play = function(){
$scope.counter++;
$scope.time.to = new Date();
$scope.time.from = time_ago($scope.timespan);
$scope.$root.$eval()
mytimeout = $timeout($scope.play,config.refresh);
}
$scope.pause = function(){
if($scope.playing) {
$scope.playing = false;
$timeout.cancel(mytimeout);
} else {
$scope.playing = true;
mytimeout = $timeout($scope.play,config.refresh);
}
}
var mytimeout = $timeout($scope.play,config.refresh);
// If from/to to change, update index list
$scope.$watch(function() {
return angular.toJson([$scope.time.from, $scope.time.to])
}, function(){
indices($scope.time.from,$scope.time.to).then(function (p) {
$scope.index = p.join();
});
});
*/
// point to your ElasticSearch server
var ejs = $scope.ejs = ejsResource(config.elasticsearch); var ejs = $scope.ejs = ejsResource(config.elasticsearch);
$scope.toggle_row = function(row) { $scope.toggle_row = function(row) {
$scope.$broadcast('toggle_row',row) $scope.$broadcast('toggle_row',row)
row.collapse = row.collapse ? false : true; row.collapse = row.collapse ? false : true;
} }
$scope.set_timespan = function(timespan) {
$scope.timespan = timespan;
$scope.time.from = time_ago($scope.timespan);
}
// returns a promise containing an array of all indices matching the index
// pattern that exist in a given range
function indices(from,to) {
var possible = [];
_.each(date_range(from,to.add_days(1)),function(d){
possible.push(d.format(config.indexpattern));
});
return all_indices().then(function(p) {
return _.intersection(p,possible);
})
};
// returns a promise containing an array of all indices in an elasticsearch
// cluster
function all_indices() {
var something = $http({
url: config.elasticsearch + "/_aliases",
method: "GET"
}).error(function(data, status, headers, config) {
$scope.error = status;
});
return something.then(function(p) {
var indices = [];
_.each(p.data, function(v,k) {
indices.push(k)
});
return indices;
});
}
}); });
......
<div ng-controller='fields'>
<h4 ng-class="{'ng-cloak': !panel.title}">{{panel.title}}</h4>
<ul class="nav nav-list" style="height:{{row.height}};overflow-y:auto;overflow-x:hidden;">
<li ng-style="panel.style" ng-repeat="field in fields" ng-class="is_active(field)" ng-click="toggle_field(field)"><a>{{field}}</a></li>
</ul>
</div>
\ No newline at end of file
angular.module('kibana.fields', [])
.controller('fields', function($scope, $rootScope) {
var _id = _.uniqueId();
// Set and populate defaults
var _d = {
group : "default",
style : {"font-size":"85%","line-height":"15px"},
}
_.defaults($scope.panel,_d);
$scope.init = function() {
$scope.fields = [];
$scope.$on($scope.panel.group+"-fields", function(event, fields) {
$scope.panel.sort = _.clone(fields.sort);
$scope.fields = _.union(fields.all,$scope.fields);
$scope.active = _.clone(fields.active);
});
}
$scope.toggle_sort = function() {
$scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc';
}
$scope.toggle_field = function(field) {
if (_.indexOf($scope.active,field) > -1)
$scope.active = _.without($scope.active,field)
else
$scope.active.push(field)
$rootScope.$broadcast($scope.panel.group+"-selected_fields",$scope.active)
}
$scope.is_active = function(field) {
return _.indexOf($scope.active,field) > -1 ? 'active' : '';
}
$scope.init();
})
\ No newline at end of file
...@@ -39,8 +39,7 @@ angular.module('kibana.histogram', []) ...@@ -39,8 +39,7 @@ angular.module('kibana.histogram', [])
ejs.QueryStringQuery(v.query || '*'), ejs.QueryStringQuery(v.query || '*'),
ejs.RangeFilter($scope.panel.time.field) ejs.RangeFilter($scope.panel.time.field)
.from($scope.panel.time.from) .from($scope.panel.time.from)
.to($scope.panel.time.to) .to($scope.panel.time.to))
.cache(false))
) )
}); });
......
<div ng-controller='hits'>
<h4 ng-class="{'ng-cloak': !panel.title}">{{panel.title}}</h4>
<p ng-style="panel.style">{{hits}}</p>
</div>
\ No newline at end of file
angular.module('kibana.hits', [])
.controller('hits', function($scope, $rootScope, $location) {
var _id = _.uniqueId();
// Set and populate defaults
var _d = {
query : "*",
group : "default",
style : { "font-size": '36pt', "font-weight": "bold" },
}
_.defaults($scope.panel,_d)
$scope.init = function () {
$scope.$on(_id+"-time", function(event,time){set_time(time)});
$scope.$on($scope.panel.group+"-time", function(event,time){set_time(time)});
$scope.$on($scope.panel.group+"-query", function(event, query) {
$scope.panel.query = query;
$scope.get_data();
});
// Now that we're all setup, request the time from our group
$rootScope.$broadcast($scope.panel.group+"-get_time",_id)
}
$scope.get_data = function() {
// Make sure we have everything for the request to complete
if(_.isUndefined($scope.panel.index) || _.isUndefined($scope.panel.time))
return
var request = $scope.ejs.Request().indices($scope.panel.index);
var results = request
.query(ejs.FilteredQuery(
ejs.QueryStringQuery($scope.panel.query || '*'),
ejs.RangeFilter(config.timefield)
.from($scope.panel.time.from)
.to($scope.panel.time.to)
)
)
.size(0)
.doSearch();
// Populate scope when we have results
results.then(function(results) {
if(_.isUndefined(results)) {
$scope.panel.error = 'Your query was unsuccessful';
return;
}
$scope.panel.error = false;
$scope.hits = results.hits.total;
});
}
function set_time(time) {
$scope.panel.time = time;
$scope.panel.index = _.isUndefined(time.index) ? $scope.panel.index : time.index
$scope.get_data();
}
$scope.init();
})
...@@ -44,7 +44,6 @@ angular.module('kibana.map', []) ...@@ -44,7 +44,6 @@ angular.module('kibana.map', [])
ejs.RangeFilter(config.timefield) ejs.RangeFilter(config.timefield)
.from($scope.panel.time.from) .from($scope.panel.time.from)
.to($scope.panel.time.to) .to($scope.panel.time.to)
.cache(false)
)))).size(0) )))).size(0)
.doSearch(); .doSearch();
......
...@@ -48,8 +48,7 @@ angular.module('kibana.pie', []) ...@@ -48,8 +48,7 @@ angular.module('kibana.pie', [])
ejs.QueryStringQuery(v.query || '*'), ejs.QueryStringQuery(v.query || '*'),
ejs.RangeFilter(config.timefield) ejs.RangeFilter(config.timefield)
.from($scope.panel.time.from) .from($scope.panel.time.from)
.to($scope.panel.time.to) .to($scope.panel.time.to))
.cache(false))
) )
}); });
...@@ -176,11 +175,10 @@ angular.module('kibana.pie', []) ...@@ -176,11 +175,10 @@ angular.module('kibana.pie', [])
function piett(x, y, contents) { function piett(x, y, contents) {
var tooltip = $('#pie-tooltip').length ? var tooltip = $('#pie-tooltip').length ?
$('#pie-tooltip') : $('<div id="pie-tooltip"></div>'); $('#pie-tooltip') : $('<div id="pie-tooltip"></div>');
//var tooltip = $('#pie-tooltip')
tooltip.text(contents).css({ tooltip.text(contents).css({
position: 'absolute', position: 'absolute',
top : y + 5, top : y + 10,
left : x + 5, left : x + 10,
color : "#FFF", color : "#FFF",
border : '1px solid #FFF', border : '1px solid #FFF',
padding : '2px', padding : '2px',
......
<div ng-controller='sort' style="white-space: nowrap;"> <div ng-controller='sort' style="white-space: nowrap;">
<h4 ng-class="{'ng-cloak': !panel.title}">{{panel.title}}</h4> <h4 ng-class="{'ng-cloak': !panel.title}">{{panel.title}}</h4>
<label><small>{{panel.label}}</small></label> <label><small>{{panel.label}}</small></label>
<select style="width:85%" ng-model="panel.sort[0]" ng-options="f for f in fields"></select> <select style="width:85%" ng-model="panel.sort[0]" ng-change="set_sort()" ng-options="f for f in fields"></select>
<i ng-click="toggle_sort()" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i> <i ng-click="toggle_sort()" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i>
</div> </div>
\ No newline at end of file
...@@ -16,13 +16,18 @@ angular.module('kibana.sort', []) ...@@ -16,13 +16,18 @@ angular.module('kibana.sort', [])
$scope.init = function() { $scope.init = function() {
$scope.fields = []; $scope.fields = [];
$scope.$on($scope.panel.group+"-fields", function(event, fields) { $scope.$on($scope.panel.group+"-fields", function(event, fields) {
$scope.panel.sort = fields.sort; $scope.panel.sort = _.clone(fields.sort);
$scope.fields = _.union(fields.all,$scope.fields); $scope.fields = _.union(fields.all,$scope.fields);
}); });
} }
$scope.set_sort = function() {
$rootScope.$broadcast($scope.panel.group+"-sort",$scope.panel.sort)
}
$scope.toggle_sort = function() { $scope.toggle_sort = function() {
$scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc'; $scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc';
$rootScope.$broadcast($scope.panel.group+"-sort",$scope.panel.sort)
} }
$scope.init(); $scope.init();
}) })
\ No newline at end of file
...@@ -13,10 +13,15 @@ angular.module('kibana.stringquery', []) ...@@ -13,10 +13,15 @@ angular.module('kibana.stringquery', [])
} }
_.defaults($scope.panel,_d); _.defaults($scope.panel,_d);
var _groups = _.isArray($scope.panel.group) ?
$scope.panel.group : [$scope.panel.group];
$scope.init = function() { $scope.init = function() {
$scope.send_query = function(query) { $scope.send_query = function(query) {
$rootScope.$broadcast($scope.panel.group+"-query", query) _.each(_groups,function(group) {
} $rootScope.$broadcast(group+"-query", query)
});
}
} }
$scope.init(); $scope.init();
}); });
\ No newline at end of file
<div ng-controller='table'> <div ng-controller='table'>
<h4>{{panel.title}}</h4> <h4>{{panel.title}}</h4>
<div style="height:{{row.height}};overflow-y:auto;overflow-x:hidden"> <div style="height:{{row.height}};overflow-y:auto;overflow-x:auto">
<table class="table table-condensed table-striped"> <table class="table table-condensed table-striped" ng-style="panel.style">
<thead> <thead>
<th ng-repeat="field in panel.fields" class="pointer" ng-click="panel.sort[0] = field"> <th style="white-space:nowrap" ng-repeat="field in panel.fields">
{{field}} <span class="pointer" ng-click="set_sort(field)">
<i ng-show='field == panel.sort[0]' ng-click="toggle_sort()" class="pointer" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i> {{field}}
<i ng-show='field == panel.sort[0]' class="pointer" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i>
</span>
</th> </th>
</thead> </thead>
<tr ng-repeat="row in data"> <tr ng-repeat="row in data">
......
...@@ -9,6 +9,8 @@ angular.module('kibana.table', []) ...@@ -9,6 +9,8 @@ angular.module('kibana.table', [])
size : 100, size : 100,
sort : ['@timestamp','desc'], sort : ['@timestamp','desc'],
group : "default", group : "default",
style : {},
fields : [],
} }
_.defaults($scope.panel,_d) _.defaults($scope.panel,_d)
...@@ -16,9 +18,17 @@ angular.module('kibana.table', []) ...@@ -16,9 +18,17 @@ angular.module('kibana.table', [])
$scope.$on(_id+"-time", function(event,time){set_time(time)}); $scope.$on(_id+"-time", function(event,time){set_time(time)});
$scope.$on($scope.panel.group+"-time", function(event,time){set_time(time)}); $scope.$on($scope.panel.group+"-time", function(event,time){set_time(time)});
$scope.$on($scope.panel.group+"-query", function(event, query) { $scope.$on($scope.panel.group+"-query", function(event, query) {
console.log($scope.panel)
$scope.panel.query = query; $scope.panel.query = query;
$scope.get_data(); $scope.get_data();
}); });
$scope.$on($scope.panel.group+"-sort", function(event,sort){
$scope.panel.sort = _.clone(sort);
});
$scope.$on($scope.panel.group+"-selected_fields", function(event, fields) {
$scope.panel.fields = _.clone(fields)
});
$scope.$watch(function() { $scope.$watch(function() {
return angular.toJson($scope.panel.sort) return angular.toJson($scope.panel.sort)
}, function(){$scope.get_data()}); }, function(){$scope.get_data()});
...@@ -26,8 +36,11 @@ angular.module('kibana.table', []) ...@@ -26,8 +36,11 @@ angular.module('kibana.table', [])
$rootScope.$broadcast($scope.panel.group+"-get_time",_id) $rootScope.$broadcast($scope.panel.group+"-get_time",_id)
} }
$scope.toggle_sort = function() { $scope.set_sort = function(field) {
$scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc'; if($scope.panel.sort[0] === field)
$scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc';
else
$scope.panel.sort[0] = field;
} }
$scope.get_data = function() { $scope.get_data = function() {
...@@ -43,7 +56,6 @@ angular.module('kibana.table', []) ...@@ -43,7 +56,6 @@ angular.module('kibana.table', [])
ejs.RangeFilter(config.timefield) ejs.RangeFilter(config.timefield)
.from($scope.panel.time.from) .from($scope.panel.time.from)
.to($scope.panel.time.to) .to($scope.panel.time.to)
.cache(false)
) )
) )
.size($scope.panel.size) .size($scope.panel.size)
...@@ -67,11 +79,16 @@ angular.module('kibana.table', []) ...@@ -67,11 +79,16 @@ angular.module('kibana.table', [])
$rootScope.$broadcast( $rootScope.$broadcast(
$scope.panel.group+"-fields", { $scope.panel.group+"-fields", {
all : get_all_fields(results), all : get_all_fields(results),
sort : $scope.panel.sort sort : $scope.panel.sort,
active: $scope.panel.fields
}); });
}); });
} }
$scope.move_field = function(field,dir) {
console.log(field,dir)
}
function set_time(time) { function set_time(time) {
$scope.panel.time = time; $scope.panel.time = time;
$scope.panel.index = _.isUndefined(time.index) ? $scope.panel.index : time.index $scope.panel.index = _.isUndefined(time.index) ? $scope.panel.index : time.index
...@@ -80,4 +97,4 @@ angular.module('kibana.table', []) ...@@ -80,4 +97,4 @@ angular.module('kibana.table', [])
$scope.init(); $scope.init();
}) });
\ No newline at end of file
<div ng-controller='text'> <div ng-controller='text'>
<h4 ng-class="{'ng-cloak': !panel.title}">{{panel.title}}</h4> <h4 ng-class="{'ng-cloak': !panel.title}">{{panel.title}}</h4>
<p style="font-size:{{panel.fontsize}}">{{panel.content}}</p> <p ng-style="panel.style">{{panel.content}}</p>
</div> </div>
\ No newline at end of file
...@@ -7,7 +7,7 @@ angular.module('kibana.text', []) ...@@ -7,7 +7,7 @@ angular.module('kibana.text', [])
var _d = { var _d = {
group : "default", group : "default",
content : "", content : "",
'fontsize': "100%" style: {},
} }
_.defaults($scope.panel,_d); _.defaults($scope.panel,_d);
......
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
</div> </div>
<div class="row-fluid" style="padding-top:10px" ng-hide="row.collapse"> <div class="row-fluid" style="padding-top:10px" ng-hide="row.collapse">
<!-- Panels --> <!-- Panels -->
<div ng-repeat="(name, panel) in row.panels" class="span{{panel.span}}" style="min-height:{{row.height}}; position:relative"> <div ng-repeat="(name, panel) in row.panels" ng-hide="panel.span == 0" class="span{{panel.span}}" style="min-height:{{row.height}}; position:relative">
<!-- Error Panel --> <!-- Error Panel -->
<div class="row-fluid"> <div class="row-fluid">
<div class="span12 alert alert-error panel-error" ng-class="{'ng-cloak': !panel.error}"> <div class="span12 alert alert-error panel-error" ng-hide="!panel.error">
<a class="close" ng-click="panel.error=false">&times;</a> <a class="close" ng-click="panel.error=false">&times;</a>
<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}} <i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment