kibana-sinatra 0.0.8 → 3.0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kibana/assets/app/components/kbn.js +3 -3
- data/lib/kibana/assets/app/controllers/dash.js +2 -19
- data/lib/kibana/assets/app/dashboards/guided.json +1 -1
- data/lib/kibana/assets/app/panels/bettermap/module.html +1 -1
- data/lib/kibana/assets/app/panels/bettermap/module.js +11 -4
- data/lib/kibana/assets/app/panels/column/module.js +3 -0
- data/lib/kibana/assets/app/panels/{pie → goal}/editor.html +1 -25
- data/lib/kibana/assets/app/panels/{pie → goal}/module.html +4 -4
- data/lib/kibana/assets/app/panels/goal/module.js +252 -0
- data/lib/kibana/assets/app/panels/histogram/interval.js +1 -1
- data/lib/kibana/assets/app/panels/histogram/module.html +12 -3
- data/lib/kibana/assets/app/panels/histogram/module.js +16 -14
- data/lib/kibana/assets/app/panels/hits/module.js +7 -3
- data/lib/kibana/assets/app/panels/map/module.html +1 -1
- data/lib/kibana/assets/app/panels/map/module.js +9 -1
- data/lib/kibana/assets/app/panels/query/module.html +1 -1
- data/lib/kibana/assets/app/panels/sparklines/interval.js +1 -1
- data/lib/kibana/assets/app/panels/sparklines/module.js +5 -1
- data/lib/kibana/assets/app/panels/table/editor.html +1 -1
- data/lib/kibana/assets/app/panels/table/micropanel.html +3 -3
- data/lib/kibana/assets/app/panels/table/module.html +63 -16
- data/lib/kibana/assets/app/panels/table/module.js +4 -1
- data/lib/kibana/assets/app/panels/terms/module.js +7 -4
- data/lib/kibana/assets/app/panels/text/module.js +2 -0
- data/lib/kibana/assets/app/panels/timepicker/module.js +9 -1
- data/lib/kibana/assets/app/panels/trends/module.js +4 -0
- data/lib/kibana/assets/app/partials/dashboard.html +4 -2
- data/lib/kibana/assets/app/partials/panelgeneral.html +1 -1
- data/lib/kibana/assets/app/services/dashboard.js +9 -7
- data/lib/kibana/assets/app/services/fields.js +34 -3
- data/lib/kibana/assets/app/services/kbnIndex.js +5 -3
- data/lib/kibana/assets/css/bootstrap.dark.min.css +1 -1
- data/lib/kibana/assets/css/bootstrap.light.min.css +1 -1
- data/lib/kibana/assets/favicon.ico +0 -0
- data/lib/kibana/assets/img/annotation-icon.png +0 -0
- data/lib/kibana/assets/index.html +14 -9
- data/lib/kibana/assets/vendor/bootstrap/less/overrides.less +11 -5
- data/lib/kibana/assets/vendor/bootstrap/less/tooltip.less +4 -4
- data/lib/kibana/assets/vendor/jquery/jquery.flot.events.js +15 -9
- data/lib/kibana/assets/vendor/jquery/jquery.flot.js +154 -77
- data/lib/kibana/assets/vendor/jquery/jquery.flot.pie.js +6 -6
- data/lib/kibana/sinatra/version.rb +1 -1
- data/lib/kibana/views/config.erb +8 -2
- metadata +8 -6
- data/lib/kibana/assets/app/panels/pie/module.js +0 -334
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d9b44942fdaddd855873685bacc3370c477558c
|
4
|
+
data.tar.gz: 0b2bc7f6628e4d34158a04292c5578e95b72b504
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93c52d30d9dc65f8016595114e1688ca75992ac5c5d124e21b0d9f6d30d2dd648b3db86af2793ab68351fd8b3acef95d4f001586ee295188e2a307c35a1d5bd7
|
7
|
+
data.tar.gz: 46b16ff2fe63598b93d3c68ab5ff0d66e35fe0242535ad6fdaa225827b53f5d5e8545b8342b0ce329bc24645384066521deaa864be2495e26ce469e6b6b678c3
|
@@ -228,19 +228,19 @@ function($, _, moment) {
|
|
228
228
|
return {
|
229
229
|
sec: kbn.intervals_in_seconds[matches[2]],
|
230
230
|
type: matches[2],
|
231
|
-
count:
|
231
|
+
count: parseFloat(matches[1])
|
232
232
|
};
|
233
233
|
}
|
234
234
|
};
|
235
235
|
|
236
236
|
kbn.interval_to_ms = function(string) {
|
237
237
|
var info = kbn.describe_interval(string);
|
238
|
-
return info.sec * 1000 * info.count;
|
238
|
+
return Math.ceil(info.sec * 1000 * info.count);
|
239
239
|
};
|
240
240
|
|
241
241
|
kbn.interval_to_seconds = function (string) {
|
242
242
|
var info = kbn.describe_interval(string);
|
243
|
-
return info.sec * info.count;
|
243
|
+
return Math.ceil(info.sec * info.count);
|
244
244
|
};
|
245
245
|
|
246
246
|
// This should go away, moment.js can do this
|
@@ -1,22 +1,3 @@
|
|
1
|
-
/** @scratch /index/0
|
2
|
-
* = Kibana
|
3
|
-
*
|
4
|
-
* // Why can't I have a preamble here?
|
5
|
-
*
|
6
|
-
* == Introduction
|
7
|
-
*
|
8
|
-
* Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for
|
9
|
-
* ElasticSearch. Kibana is a snap to setup and start using. Written entirely in HTML and Javascript
|
10
|
-
* it requires only a plain webserver, Kibana requires no fancy server side components.
|
11
|
-
* Kibana strives to be easy to get started with, while also being flexible and powerful, just like
|
12
|
-
* Elasticsearch.
|
13
|
-
*
|
14
|
-
* include::configuration/config.js.asciidoc[]
|
15
|
-
*
|
16
|
-
* include::panels.asciidoc[]
|
17
|
-
*
|
18
|
-
*/
|
19
|
-
|
20
1
|
define([
|
21
2
|
'angular',
|
22
3
|
'config',
|
@@ -31,6 +12,8 @@ function (angular, config, _) {
|
|
31
12
|
module.controller('DashCtrl', function(
|
32
13
|
$scope, $route, ejsResource, fields, dashboard, alertSrv, panelMove, esVersion, kbnVersion) {
|
33
14
|
|
15
|
+
$scope.Math = Math;
|
16
|
+
|
34
17
|
$scope.editor = {
|
35
18
|
index: 0
|
36
19
|
};
|
@@ -6,6 +6,6 @@
|
|
6
6
|
</style>
|
7
7
|
<!-- This solution might work well for other panels that have trouble with heights -->
|
8
8
|
<div style="padding-right:10px;padding-top:10px;height:{{panel.height|| row.height}};overflow:hidden">
|
9
|
-
<div bettermap id=
|
9
|
+
<div bettermap id="{{$id}}" params="{{panel}}" style="height:100%"></div>
|
10
10
|
</div>
|
11
11
|
</div>
|
@@ -1,8 +1,10 @@
|
|
1
1
|
/** @scratch /panels/5
|
2
|
+
*
|
2
3
|
* include::panels/bettermap.asciidoc[]
|
3
4
|
*/
|
4
5
|
|
5
6
|
/** @scratch /panels/bettermap/0
|
7
|
+
*
|
6
8
|
* == Bettermap
|
7
9
|
* Status: *Experimental*
|
8
10
|
*
|
@@ -60,6 +62,7 @@ function (angular, app, _, L, localRequire) {
|
|
60
62
|
// Set and populate defaults
|
61
63
|
var _d = {
|
62
64
|
/** @scratch /panels/bettermap/3
|
65
|
+
*
|
63
66
|
* === Parameters
|
64
67
|
*
|
65
68
|
* field:: The field that contains the coordinates, in geojson format. GeoJSON is
|
@@ -80,6 +83,7 @@ function (angular, app, _, L, localRequire) {
|
|
80
83
|
*/
|
81
84
|
tooltip : "_id",
|
82
85
|
/** @scratch /panels/bettermap/5
|
86
|
+
*
|
83
87
|
* ==== Queries
|
84
88
|
* queries object:: This object describes the queries to use on this panel.
|
85
89
|
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
@@ -205,8 +209,7 @@ function (angular, app, _, L, localRequire) {
|
|
205
209
|
module.directive('bettermap', function() {
|
206
210
|
return {
|
207
211
|
restrict: 'A',
|
208
|
-
link: function(scope, elem
|
209
|
-
|
212
|
+
link: function(scope, elem) {
|
210
213
|
elem.html('<center><img src="img/load_big.gif"></center>');
|
211
214
|
|
212
215
|
// Receive render events
|
@@ -224,18 +227,22 @@ function (angular, app, _, L, localRequire) {
|
|
224
227
|
var map, layerGroup;
|
225
228
|
|
226
229
|
function render_panel() {
|
230
|
+
elem.css({height:scope.row.height});
|
231
|
+
|
227
232
|
scope.require(['./leaflet/plugins'], function () {
|
228
233
|
scope.panelMeta.loading = false;
|
229
234
|
L.Icon.Default.imagePath = 'app/panels/bettermap/leaflet/images';
|
230
235
|
if(_.isUndefined(map)) {
|
231
|
-
map = L.map(
|
236
|
+
map = L.map(scope.$id, {
|
232
237
|
scrollWheelZoom: false,
|
233
238
|
center: [40, -86],
|
234
239
|
zoom: 10
|
235
240
|
});
|
236
241
|
|
237
242
|
// This could be made configurable?
|
238
|
-
L.tileLayer('
|
243
|
+
L.tileLayer('http://otile1.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg', {
|
244
|
+
attribution: '"Data, imagery and map information provided by MapQuest, '+
|
245
|
+
'OpenStreetMap <http://www.openstreetmap.org/copyright> and contributors, ODbL',
|
239
246
|
maxZoom: 18,
|
240
247
|
minZoom: 2
|
241
248
|
}).addTo(map);
|
@@ -1,8 +1,10 @@
|
|
1
1
|
/** @scratch /panels/5
|
2
|
+
*
|
2
3
|
* include::panels/column.asciidoc[]
|
3
4
|
*/
|
4
5
|
|
5
6
|
/** @scratch /panels/column/0
|
7
|
+
*
|
6
8
|
* == Column
|
7
9
|
* Status: *Stable*
|
8
10
|
*
|
@@ -34,6 +36,7 @@ function (angular, app, _, config) {
|
|
34
36
|
// Set and populate defaults
|
35
37
|
var _d = {
|
36
38
|
/** @scratch /panels/column/3
|
39
|
+
*
|
37
40
|
* === Parameters
|
38
41
|
*
|
39
42
|
* panel:: An array of panel objects
|
@@ -1,30 +1,6 @@
|
|
1
|
-
<div class="editor-row"
|
1
|
+
<div class="editor-row">
|
2
2
|
|
3
3
|
<div class="section">
|
4
|
-
<h5>Mode</h5>
|
5
|
-
<div class="editor-option">
|
6
|
-
<label class="small">Mode</label>
|
7
|
-
<select class="input-small" ng-change="set_mode(panel.mode);set_refresh(true)" ng-model="panel.mode" ng-options="f for f in ['terms','goal']"></select>
|
8
|
-
</div>
|
9
|
-
</div>
|
10
|
-
|
11
|
-
<div class="section" ng-switch-when="terms">
|
12
|
-
<h5>Parameters</h5>
|
13
|
-
<div class="editor-option">
|
14
|
-
<label class="small">Field</label>
|
15
|
-
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.query.field" ng-change="set_refresh(true)">
|
16
|
-
</div>
|
17
|
-
<div class="editor-option">
|
18
|
-
<label class="small">Length</label>
|
19
|
-
<input class="input-mini" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
|
20
|
-
</div>
|
21
|
-
<div class="editor-option">
|
22
|
-
<label class="small">Exclude Terms(s) (comma separated)</label>
|
23
|
-
<input array-join type="text" ng-model='panel.exclude'></input>
|
24
|
-
</div>
|
25
|
-
</div>
|
26
|
-
|
27
|
-
<div class="section" ng-switch-when="goal">
|
28
4
|
<h5>Parameters</h5>
|
29
5
|
<div class="editor-option">
|
30
6
|
<form style="margin-bottom: 0px">
|
@@ -1,13 +1,13 @@
|
|
1
|
-
<div ng-controller='
|
1
|
+
<div ng-controller='goal' ng-init="init()">
|
2
2
|
<style>
|
3
|
-
.
|
3
|
+
.goal-label { pointer-events: none }
|
4
4
|
</style>
|
5
5
|
<div ng-show="panel.legend == 'above'" ng-repeat="query in legend" style="float:left;padding-left: 10px;">
|
6
6
|
<span ng-show="panel.chart != 'none'"><i class="icon-circle" ng-style="{color:query.color}"></i></span><span class="small"> {{query.label}} ({{query.data[0][1]}}) </span></span>
|
7
|
-
</div
|
7
|
+
</div>
|
8
8
|
<div style="clear:both"></div>
|
9
9
|
|
10
|
-
<div
|
10
|
+
<div goal params="{{panel}}" style="position:relative"></div>
|
11
11
|
|
12
12
|
<div ng-show="panel.legend == 'below'" ng-repeat="query in legend" style="float:left;padding-left: 10px;">
|
13
13
|
<span ng-show="panel.chart != 'none'"><i class="icon-circle" ng-style="{color:query.color}"></i></span><span class="small"> {{query.label}} ({{query.data[0][1]}}) </span></span>
|
@@ -0,0 +1,252 @@
|
|
1
|
+
/** @scratch /panels/5
|
2
|
+
*
|
3
|
+
* include::panels/goal.asciidoc[]
|
4
|
+
*/
|
5
|
+
|
6
|
+
/** @scratch /panels/goal/0
|
7
|
+
*
|
8
|
+
* == Goal
|
9
|
+
* Status: *Stable*
|
10
|
+
*
|
11
|
+
* The goal panel display progress towards a fixed goal on a pie chart
|
12
|
+
*
|
13
|
+
*/
|
14
|
+
define([
|
15
|
+
'angular',
|
16
|
+
'app',
|
17
|
+
'lodash',
|
18
|
+
'jquery',
|
19
|
+
'kbn',
|
20
|
+
'config',
|
21
|
+
'chromath'
|
22
|
+
], function (angular, app, _, $, kbn) {
|
23
|
+
'use strict';
|
24
|
+
|
25
|
+
var module = angular.module('kibana.panels.goal', []);
|
26
|
+
app.useModule(module);
|
27
|
+
|
28
|
+
module.controller('goal', function($scope, $rootScope, querySrv, dashboard, filterSrv) {
|
29
|
+
|
30
|
+
$scope.panelMeta = {
|
31
|
+
editorTabs : [
|
32
|
+
{title:'Queries', src:'app/partials/querySelect.html'}
|
33
|
+
],
|
34
|
+
modals : [
|
35
|
+
{
|
36
|
+
description: "Inspect",
|
37
|
+
icon: "icon-info-sign",
|
38
|
+
partial: "app/partials/inspector.html",
|
39
|
+
show: $scope.panel.spyable
|
40
|
+
}
|
41
|
+
],
|
42
|
+
status : "Stable",
|
43
|
+
description : "Displays the progress towards a fixed goal on a pie chart"
|
44
|
+
};
|
45
|
+
|
46
|
+
// Set and populate defaults
|
47
|
+
var _d = {
|
48
|
+
/** @scratch /panels/goal/3
|
49
|
+
*
|
50
|
+
* === Parameters
|
51
|
+
* donut:: Draw a hole in the middle of the pie, creating a tasty donut.
|
52
|
+
*/
|
53
|
+
donut : true,
|
54
|
+
/** @scratch /panels/goal/3
|
55
|
+
* tilt:: Tilt the pie back into an oval shape
|
56
|
+
*/
|
57
|
+
tilt : false,
|
58
|
+
/** @scratch /panels/goal/3
|
59
|
+
* legend:: The location of the legend, above, below or none
|
60
|
+
*/
|
61
|
+
legend : "above",
|
62
|
+
/** @scratch /panels/goal/3
|
63
|
+
* labels:: Set to false to disable drawing labels inside the pie slices
|
64
|
+
*/
|
65
|
+
labels : true,
|
66
|
+
/** @scratch /panels/goal/3
|
67
|
+
* spyable:: Set to false to disable the inspect function.
|
68
|
+
*/
|
69
|
+
spyable : true,
|
70
|
+
/** @scratch /panels/goal/3
|
71
|
+
*
|
72
|
+
* ==== Query
|
73
|
+
*
|
74
|
+
* query object::
|
75
|
+
* query.goal::: the fixed goal for goal mode
|
76
|
+
*/
|
77
|
+
query : {goal: 100},
|
78
|
+
/** @scratch /panels/goal/5
|
79
|
+
*
|
80
|
+
* ==== Queries
|
81
|
+
*
|
82
|
+
* queries object:: This object describes the queries to use on this panel.
|
83
|
+
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
84
|
+
* queries.ids::: In +selected+ mode, which query ids are selected.
|
85
|
+
*/
|
86
|
+
queries : {
|
87
|
+
mode : 'all',
|
88
|
+
ids : []
|
89
|
+
},
|
90
|
+
};
|
91
|
+
_.defaults($scope.panel,_d);
|
92
|
+
|
93
|
+
$scope.init = function() {
|
94
|
+
$scope.$on('refresh',function(){$scope.get_data();});
|
95
|
+
$scope.get_data();
|
96
|
+
};
|
97
|
+
|
98
|
+
$scope.set_refresh = function (state) {
|
99
|
+
$scope.refresh = state;
|
100
|
+
};
|
101
|
+
|
102
|
+
$scope.close_edit = function() {
|
103
|
+
if($scope.refresh) {
|
104
|
+
$scope.get_data();
|
105
|
+
}
|
106
|
+
$scope.refresh = false;
|
107
|
+
$scope.$emit('render');
|
108
|
+
};
|
109
|
+
|
110
|
+
$scope.get_data = function() {
|
111
|
+
|
112
|
+
// Make sure we have everything for the request to complete
|
113
|
+
if(dashboard.indices.length === 0) {
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
|
117
|
+
|
118
|
+
$scope.panelMeta.loading = true;
|
119
|
+
var request = $scope.ejs.Request().indices(dashboard.indices);
|
120
|
+
|
121
|
+
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
122
|
+
var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
123
|
+
|
124
|
+
// This could probably be changed to a BoolFilter
|
125
|
+
var boolQuery = $scope.ejs.BoolQuery();
|
126
|
+
_.each(queries,function(q) {
|
127
|
+
boolQuery = boolQuery.should(querySrv.toEjsObj(q));
|
128
|
+
});
|
129
|
+
|
130
|
+
var results;
|
131
|
+
|
132
|
+
request = request
|
133
|
+
.query(boolQuery)
|
134
|
+
.filter(filterSrv.getBoolFilter(filterSrv.ids))
|
135
|
+
.size(0);
|
136
|
+
|
137
|
+
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
138
|
+
|
139
|
+
results = request.doSearch();
|
140
|
+
|
141
|
+
results.then(function(results) {
|
142
|
+
$scope.panelMeta.loading = false;
|
143
|
+
var complete = results.hits.total;
|
144
|
+
var remaining = $scope.panel.query.goal - complete;
|
145
|
+
$scope.data = [
|
146
|
+
{ label : 'Complete', data : complete, color: querySrv.colors[parseInt($scope.$id, 16)%8] },
|
147
|
+
{ data : remaining, color: Chromath.lighten(querySrv.colors[parseInt($scope.$id, 16)%8],0.70).toString() }
|
148
|
+
];
|
149
|
+
$scope.$emit('render');
|
150
|
+
});
|
151
|
+
};
|
152
|
+
|
153
|
+
});
|
154
|
+
|
155
|
+
module.directive('goal', function(querySrv) {
|
156
|
+
return {
|
157
|
+
restrict: 'A',
|
158
|
+
link: function(scope, elem) {
|
159
|
+
|
160
|
+
elem.html('<center><img src="img/load_big.gif"></center>');
|
161
|
+
|
162
|
+
// Receive render events
|
163
|
+
scope.$on('render',function(){
|
164
|
+
render_panel();
|
165
|
+
});
|
166
|
+
|
167
|
+
// Or if the window is resized
|
168
|
+
angular.element(window).bind('resize', function(){
|
169
|
+
render_panel();
|
170
|
+
});
|
171
|
+
|
172
|
+
// Function for rendering panel
|
173
|
+
function render_panel() {
|
174
|
+
// IE doesn't work without this
|
175
|
+
elem.css({height:scope.row.height});
|
176
|
+
|
177
|
+
var label;
|
178
|
+
|
179
|
+
label = {
|
180
|
+
show: scope.panel.labels,
|
181
|
+
radius: 0,
|
182
|
+
formatter: function(label, series){
|
183
|
+
var font = parseInt(scope.row.height.replace('px',''),10)/8 + String('px');
|
184
|
+
if(!(_.isUndefined(label))) {
|
185
|
+
return '<div style="font-size:'+font+';font-weight:bold;text-align:center;padding:2px;color:#fff;">'+
|
186
|
+
Math.round(series.percent)+'%</div>';
|
187
|
+
} else {
|
188
|
+
return '';
|
189
|
+
}
|
190
|
+
},
|
191
|
+
};
|
192
|
+
|
193
|
+
var pie = {
|
194
|
+
series: {
|
195
|
+
pie: {
|
196
|
+
innerRadius: scope.panel.donut ? 0.45 : 0,
|
197
|
+
tilt: scope.panel.tilt ? 0.45 : 1,
|
198
|
+
radius: 1,
|
199
|
+
show: true,
|
200
|
+
combine: {
|
201
|
+
color: '#999',
|
202
|
+
label: 'The Rest'
|
203
|
+
},
|
204
|
+
label: label,
|
205
|
+
stroke: {
|
206
|
+
width: 0
|
207
|
+
}
|
208
|
+
}
|
209
|
+
},
|
210
|
+
//grid: { hoverable: true, clickable: true },
|
211
|
+
grid: {
|
212
|
+
backgroundColor: null,
|
213
|
+
hoverable: true,
|
214
|
+
clickable: true
|
215
|
+
},
|
216
|
+
legend: { show: false },
|
217
|
+
colors: querySrv.colors
|
218
|
+
};
|
219
|
+
|
220
|
+
// Populate legend
|
221
|
+
if(elem.is(":visible")){
|
222
|
+
require(['jquery.flot.pie'], function(){
|
223
|
+
scope.legend = $.plot(elem, scope.data, pie).getData();
|
224
|
+
if(!scope.$$phase) {
|
225
|
+
scope.$apply();
|
226
|
+
}
|
227
|
+
});
|
228
|
+
}
|
229
|
+
|
230
|
+
}
|
231
|
+
|
232
|
+
var $tooltip = $('<div>');
|
233
|
+
elem.bind('plothover', function (event, pos, item) {
|
234
|
+
if (item) {
|
235
|
+
$tooltip
|
236
|
+
.html([
|
237
|
+
kbn.query_color_dot(item.series.color, 15),
|
238
|
+
(item.series.label || ''),
|
239
|
+
parseFloat(item.series.percent).toFixed(1) + '%'
|
240
|
+
].join(' '))
|
241
|
+
.place_tt(pos.pageX, pos.pageY, {
|
242
|
+
offset: 10
|
243
|
+
});
|
244
|
+
} else {
|
245
|
+
$tooltip.remove();
|
246
|
+
}
|
247
|
+
});
|
248
|
+
|
249
|
+
}
|
250
|
+
};
|
251
|
+
});
|
252
|
+
});
|