mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
d5ae35c3ad
15 changed files with 555 additions and 77 deletions
|
@ -4,12 +4,12 @@
|
|||
<i
|
||||
class="fa"
|
||||
ng-class="{
|
||||
'fa-check-square': !field.hidden,
|
||||
'fa-square-o': field.hidden
|
||||
'fa-check-square': field.display,
|
||||
'fa-square-o': !field.display
|
||||
}">
|
||||
</i>
|
||||
{{field.name}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<small class="pull-right"><a ng-click="refresh()">refresh field list</a></small>
|
||||
<small><a ng-click="refresh()">refresh field list</a></small>
|
|
@ -3,37 +3,41 @@
|
|||
<form class="navbar-form navbar-left form-inline" role="search" ng-submit="fetch()">
|
||||
<label class="control-label">Index</label>
|
||||
<input class="form-control" ng-model="index">
|
||||
<label class="control-label" for="size">Query</label>
|
||||
<label class="control-label">Query</label>
|
||||
<input type="text" class="form-control" ng-model="query" placeholder="search">
|
||||
<label class="control-label" for="size">Limit</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="size"
|
||||
ng-model="size"
|
||||
ng-options="size.display for size in sizeOptions">
|
||||
</select>
|
||||
<label class="control-label" for="sort">Sort</label>
|
||||
<label class="control-label">Sort</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="sort"
|
||||
ng-model="sort"
|
||||
ng-options="field.name for field in fields">
|
||||
</select>
|
||||
<label class="control-label">Max Summary Length</label>
|
||||
<input type="number" name class="form-control" ng-model="opts.maxSummaryLength">
|
||||
<button type="submit" class="btn btn-default">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<kbn-table rows="rows" columns="columns"></kbn-table>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<disc-field-chooser
|
||||
fields="fields"
|
||||
toggle="toggleField"
|
||||
refresh="refreshFieldList">
|
||||
</disc-field-chooser>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Fields</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<disc-field-chooser
|
||||
fields="fields"
|
||||
toggle="toggleField"
|
||||
refresh="refreshFieldList">
|
||||
</disc-field-chooser>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<kbn-table rows="rows" columns="columns" max-length="opts.maxSummaryLength"></kbn-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -8,16 +8,7 @@ define(function (require, module, exports) {
|
|||
|
||||
var app = angular.module('app/discover');
|
||||
|
||||
var sizeOptions = [
|
||||
{ display: '30', val: 30 },
|
||||
{ display: '50', val: 50 },
|
||||
{ display: '80', val: 80 },
|
||||
{ display: '125', val: 125 },
|
||||
{ display: '250', val: 250 },
|
||||
{ display: '500', val: 500 }
|
||||
];
|
||||
|
||||
var intervalOptions = [
|
||||
var intervals = [
|
||||
{ display: '', val: null },
|
||||
{ display: 'Hourly', val: 'hourly' },
|
||||
{ display: 'Daily', val: 'daily' },
|
||||
|
@ -34,21 +25,24 @@ define(function (require, module, exports) {
|
|||
source = savedSearches.create();
|
||||
}
|
||||
|
||||
$scope.opts = {
|
||||
// number of records to fetch, then paginate through
|
||||
sampleSize: 500,
|
||||
// max length for summaries in the table
|
||||
maxSummaryLength: 100
|
||||
};
|
||||
|
||||
// stores the complete list of fields
|
||||
$scope.fields = null;
|
||||
|
||||
// stores the fields we want to fetch
|
||||
$scope.columns = null;
|
||||
|
||||
// At what interval are your index patterns
|
||||
$scope.intervalOptions = intervalOptions;
|
||||
$scope.interval = $scope.intervalOptions[0];
|
||||
// index pattern interval options
|
||||
$scope.intervals = intervals;
|
||||
$scope.interval = $scope.intervals[0];
|
||||
|
||||
// options to control the size of the queries
|
||||
$scope.sizeOptions = sizeOptions;
|
||||
$scope.size = $scope.sizeOptions[0];
|
||||
|
||||
// the index that will be
|
||||
// the index to use when they don't specify one
|
||||
config.$watch('discover.defaultIndex', function (val) {
|
||||
if (!val) return config.set('discover.defaultIndex', '_all');
|
||||
if (!$scope.index) {
|
||||
|
@ -58,25 +52,22 @@ define(function (require, module, exports) {
|
|||
});
|
||||
|
||||
source
|
||||
.size(30)
|
||||
.$scope($scope)
|
||||
.inherits(courier.rootSearchSource)
|
||||
.on('results', function (res) {
|
||||
if (!$scope.fields) getFields();
|
||||
|
||||
$scope.rows = res.hits.hits;
|
||||
});
|
||||
|
||||
$scope.fetch = function () {
|
||||
if (!$scope.fields) getFields();
|
||||
source
|
||||
.size($scope.size.val)
|
||||
.size($scope.opts.sampleSize)
|
||||
.query(!$scope.query ? null : {
|
||||
query_string: {
|
||||
query: $scope.query
|
||||
}
|
||||
})
|
||||
.source(!$scope.columns ? null : {
|
||||
include: $scope.columns
|
||||
});
|
||||
|
||||
if ($scope.sort) {
|
||||
|
@ -115,7 +106,7 @@ define(function (require, module, exports) {
|
|||
|
||||
var currentState = _.transform($scope.fields || [], function (current, field) {
|
||||
current[field.name] = {
|
||||
hidden: field.hidden
|
||||
display: field.display
|
||||
};
|
||||
}, {});
|
||||
|
||||
|
@ -131,12 +122,12 @@ define(function (require, module, exports) {
|
|||
.each(function (name) {
|
||||
var field = fields[name];
|
||||
field.name = name;
|
||||
_.defaults(field, currentState[name]);
|
||||
|
||||
if (!field.hidden) $scope.columns.push(name);
|
||||
_.defaults(field, currentState[name]);
|
||||
$scope.fields.push(field);
|
||||
});
|
||||
|
||||
refreshColumns();
|
||||
defer.resolve();
|
||||
}, defer.reject);
|
||||
|
||||
|
@ -148,18 +139,10 @@ define(function (require, module, exports) {
|
|||
$scope.toggleField = function (name) {
|
||||
var field = _.find($scope.fields, { name: name });
|
||||
|
||||
// toggle the hidden property
|
||||
field.hidden = !field.hidden;
|
||||
// toggle the display property
|
||||
field.display = !field.display;
|
||||
|
||||
// collect column names for non-hidden fields and sort
|
||||
$scope.columns = _.transform($scope.fields, function (cols, field) {
|
||||
if (!field.hidden) cols.push(field.name);
|
||||
}, []).sort();
|
||||
|
||||
// if we are just removing a field, no reason to refetch
|
||||
if (!field.hidden) {
|
||||
$scope.fetch();
|
||||
}
|
||||
refreshColumns();
|
||||
};
|
||||
|
||||
$scope.refreshFieldList = function () {
|
||||
|
@ -170,6 +153,17 @@ define(function (require, module, exports) {
|
|||
});
|
||||
};
|
||||
|
||||
function refreshColumns() {
|
||||
// collect column names for displayed fields and sort
|
||||
$scope.columns = _.transform($scope.fields, function (cols, field) {
|
||||
if (field.display) cols.push(field.name);
|
||||
}, []).sort();
|
||||
|
||||
if (!$scope.columns.length) {
|
||||
$scope.columns.push('_source');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$emit('application.load');
|
||||
});
|
||||
});
|
5
src/kibana/apps/discover/styles/main.css
Normal file
5
src/kibana/apps/discover/styles/main.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
disc-field-chooser ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
7
src/kibana/apps/discover/styles/main.less
Normal file
7
src/kibana/apps/discover/styles/main.less
Normal file
|
@ -0,0 +1,7 @@
|
|||
disc-field-chooser {
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ define(function (require) {
|
|||
restrict: 'E',
|
||||
template: 'My favorite number is {{favoriteNum}} <button ng-click="click()">New Favorite</button>',
|
||||
controller: function ($scope, config) {
|
||||
// automatically write the value to elasticsearch when it is changed
|
||||
config.$bind($scope, 'favoriteNum', {
|
||||
default: 0
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ define(function (require) {
|
|||
$scope.activeApp = '';
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function (event, uri) {
|
||||
if (!uri) return;
|
||||
$scope.activeApp = uri.split('#')[1].split('/')[1];
|
||||
});
|
||||
|
||||
|
|
47
src/kibana/directives/infinite_scroll.js
Normal file
47
src/kibana/directives/infinite_scroll.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
define(function (require) {
|
||||
var module = require('angular').module('kibana/directives');
|
||||
var $ = require('jquery');
|
||||
|
||||
module.directive('kbnInfiniteScroll', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
more: '='
|
||||
},
|
||||
link: function ($scope, $element, attrs) {
|
||||
var $window = $(window);
|
||||
var checkTimer;
|
||||
|
||||
function onScroll() {
|
||||
if (!$scope.more) return;
|
||||
|
||||
var winHeight = $window.height();
|
||||
var winBottom = winHeight + $window.scrollTop();
|
||||
var elTop = $element.offset().top;
|
||||
var remaining = elTop - winBottom;
|
||||
|
||||
if (remaining <= winHeight * 0.50) {
|
||||
$scope[$scope.$$phase ? '$eval' : '$apply'](function () {
|
||||
$scope.more();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleCheck() {
|
||||
if (checkTimer) return;
|
||||
checkTimer = setTimeout(function () {
|
||||
checkTimer = null;
|
||||
onScroll();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
$window.on('scroll', scheduleCheck);
|
||||
$scope.$on('$destroy', function () {
|
||||
clearTimeout(checkTimer);
|
||||
$window.off('scroll', scheduleCheck);
|
||||
});
|
||||
scheduleCheck();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -2,6 +2,11 @@ define(function (require) {
|
|||
var html = require('text!partials/table.html');
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var $ = require('jquery');
|
||||
|
||||
require('directives/truncated');
|
||||
require('directives/infinite_scroll');
|
||||
|
||||
var module = angular.module('kibana/directives');
|
||||
|
||||
|
@ -11,38 +16,304 @@ define(function (require) {
|
|||
* displays results in a simple table view. Pass the result object
|
||||
* via the results attribute on the kbnTable element:
|
||||
* ```
|
||||
* <kbn-table results="queryResult"></kbn-table>
|
||||
* <kbn-table columns="columnsToDisplay" rows="rowsToDisplay"></kbn-table>
|
||||
* ```
|
||||
*/
|
||||
module.directive('kbnTable', function () {
|
||||
module.directive('kbnTable', function ($compile) {
|
||||
// base class for all dom nodes
|
||||
var DOMNode = window.Node;
|
||||
|
||||
function scheduleNextRenderTick(cb) {
|
||||
if (typeof window.requestAnimationFrame === 'function') {
|
||||
window.requestAnimationFrame(cb);
|
||||
} else {
|
||||
nextTick(cb);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: html,
|
||||
scope: {
|
||||
columns: '=',
|
||||
rows: '='
|
||||
rows: '=',
|
||||
maxLength: '=?'
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
scope.$watch('rows', render);
|
||||
scope.$watch('columns', render);
|
||||
link: function ($scope, element, attrs) {
|
||||
// track a list of id's that are currently open, so that
|
||||
// render can easily render in the same current state
|
||||
var opened = [];
|
||||
|
||||
// the current position in the list of rows
|
||||
var cursor = 0;
|
||||
|
||||
// the page size to load rows (out of the rows array, load 50 at a time)
|
||||
var pageSize = 50;
|
||||
|
||||
// rendering an entire page while the page is scrolling can cause a good
|
||||
// bit of jank, lets only render a certain amount per "tick"
|
||||
var rowsPerTick;
|
||||
|
||||
// set the maxLength for summaries
|
||||
if ($scope.maxLength === void 0) {
|
||||
$scope.maxLength = 250;
|
||||
}
|
||||
|
||||
// rerender when either is changed
|
||||
$scope.$watch('rows', render);
|
||||
$scope.$watch('columns', render);
|
||||
$scope.$watch('maxLength', render);
|
||||
|
||||
// the body of the table
|
||||
var $body = element.find('tbody');
|
||||
|
||||
// itterate the columns and rows, rebuild the table's html
|
||||
function render() {
|
||||
var $body = element.find('tbody').empty();
|
||||
$body.empty();
|
||||
if (!$scope.rows || $scope.rows.length === 0) return;
|
||||
if (!$scope.columns || $scope.columns.length === 0) return;
|
||||
cursor = 0;
|
||||
addRows();
|
||||
$scope.addRows = addRows;
|
||||
}
|
||||
|
||||
if (!scope.rows || scope.rows.length === 0) return;
|
||||
if (!scope.columns || scope.columns.length === 0) return;
|
||||
var renderRows = (function () {
|
||||
// basic buffer that will be pulled from when we are adding rows.
|
||||
var queue = [];
|
||||
var rendering = false;
|
||||
|
||||
_.each(scope.rows, function (row) {
|
||||
var tr = document.createElement('tr');
|
||||
return function renderRows(rows) {
|
||||
[].push.apply(queue, rows);
|
||||
if (!rendering) {
|
||||
onTick();
|
||||
}
|
||||
};
|
||||
|
||||
_.each(scope.columns, function (name) {
|
||||
var td = document.createElement('td');
|
||||
td.innerText = row._source[name] || row[name] || '';
|
||||
tr.appendChild(td);
|
||||
function forEachRow(row, i, currentChunk) {
|
||||
var id = rowId(row);
|
||||
var $summary = createSummaryRow(row, id);
|
||||
var $details = createDetailsRow(row, id);
|
||||
// cursor is the end of current selection, so
|
||||
// subtract the remaining queue size, then the
|
||||
// size of this chunk, and add the current i
|
||||
var currentPosition = cursor - queue.length - currentChunk.length + i;
|
||||
if (currentPosition % 2) {
|
||||
$summary.addClass('even');
|
||||
$details.addClass('even');
|
||||
}
|
||||
|
||||
$body.append([
|
||||
$summary,
|
||||
$details
|
||||
]);
|
||||
}
|
||||
|
||||
function onTick() {
|
||||
// ensure that the rendering flag is set
|
||||
rendering = true;
|
||||
var performance = window.performance;
|
||||
var timing;
|
||||
|
||||
if (
|
||||
performance
|
||||
&& rowsPerTick === void 0
|
||||
&& typeof performance.now === 'function'
|
||||
) {
|
||||
timing = performance.now();
|
||||
rowsPerTick = 30;
|
||||
}
|
||||
|
||||
queue
|
||||
// grab the first n from the buffer
|
||||
.splice(0, rowsPerTick)
|
||||
// render each row
|
||||
.forEach(forEachRow);
|
||||
|
||||
if (timing) {
|
||||
var time = performance.now() - timing;
|
||||
var rowsRendered = rowsPerTick;
|
||||
var msPerRow = time / rowsPerTick;
|
||||
// aim to fit the rendering into 5 milliseconds
|
||||
rowsPerTick = Math.ceil(5 / msPerRow);
|
||||
console.log('completed render of %d rows in %d milliseconds. rowsPerTick set to %d', rowsRendered, time, rowsPerTick);
|
||||
}
|
||||
|
||||
if (queue.length) {
|
||||
// the queue is not empty, draw again next tick
|
||||
scheduleNextRenderTick(onTick);
|
||||
} else {
|
||||
// unset the rendering flag
|
||||
rendering = false;
|
||||
}
|
||||
}
|
||||
}());
|
||||
|
||||
function addRows() {
|
||||
if (cursor > $scope.rows.length) {
|
||||
$scope.addRows = null;
|
||||
}
|
||||
renderRows($scope.rows.slice(cursor, cursor += pageSize));
|
||||
}
|
||||
|
||||
// for now, rows are "tracked" by their index, but this could eventually
|
||||
// be configured so that changing the order of the rows won't prevent
|
||||
// them from staying open on update
|
||||
function rowId(row) {
|
||||
var id = $scope.rows.indexOf(row);
|
||||
return ~id ? id : null;
|
||||
}
|
||||
|
||||
// inverse of rowId()
|
||||
function rowForId(id) {
|
||||
return $scope.rows[id];
|
||||
}
|
||||
|
||||
// toggle display of the rows details, a full list of the fields from each row
|
||||
$scope.toggleRow = function (id, event) {
|
||||
var row = rowForId(id);
|
||||
|
||||
if (~opened.indexOf(id)) {
|
||||
_.pull(opened, id);
|
||||
} else {
|
||||
opened.push(id);
|
||||
}
|
||||
|
||||
// rather than replace the entire row, just replace the
|
||||
// children, this way we keep the "even" class on the row
|
||||
appendDetailsToRow(
|
||||
$(event.delegateTarget).next().empty(),
|
||||
row,
|
||||
id
|
||||
);
|
||||
};
|
||||
|
||||
var topLevelDetails = '_index _type _id'.split(' ');
|
||||
function createDetailsRow(row, id) {
|
||||
var $tr = $(document.createElement('tr'));
|
||||
return appendDetailsToRow($tr, row, id);
|
||||
}
|
||||
|
||||
function appendDetailsToRow($tr, row, id) {
|
||||
// we need a td to wrap the details table
|
||||
var containerTd = document.createElement('td');
|
||||
containerTd.setAttribute('colspan', $scope.columns.length);
|
||||
$tr.append(containerTd);
|
||||
|
||||
var open = !!~opened.indexOf(id);
|
||||
$tr.toggle(open);
|
||||
|
||||
// it's closed, so no need to go any further
|
||||
if (!open) return $tr;
|
||||
|
||||
// table that will hold details about the row
|
||||
var table = document.createElement('table');
|
||||
containerTd.appendChild(table);
|
||||
table.className = 'table';
|
||||
|
||||
// body of the table
|
||||
var tbody = document.createElement('tbody');
|
||||
table.appendChild(tbody);
|
||||
|
||||
// itterate each row and append it to the tbody
|
||||
_(row._source)
|
||||
.keys()
|
||||
.concat(topLevelDetails)
|
||||
.sort()
|
||||
.each(function (field) {
|
||||
var tr = document.createElement('tr');
|
||||
// tr -> || <field> || <val> ||
|
||||
|
||||
var fieldTd = document.createElement('td');
|
||||
fieldTd.textContent = field;
|
||||
fieldTd.className = 'field-name';
|
||||
tr.appendChild(fieldTd);
|
||||
|
||||
var valTd = document.createElement('td');
|
||||
_displayField(valTd, row, field, true);
|
||||
tr.appendChild(valTd);
|
||||
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
$body.append(tr);
|
||||
return $tr;
|
||||
}
|
||||
|
||||
// create a tr element that lists the value for each *column*
|
||||
function createSummaryRow(row, id) {
|
||||
var tr = document.createElement('tr');
|
||||
tr.setAttribute('ng-click', 'toggleRow(' + JSON.stringify(id) + ', $event)');
|
||||
var $tr = $compile(tr)($scope);
|
||||
|
||||
_.each($scope.columns, function (column) {
|
||||
var td = document.createElement('td');
|
||||
_displayField(td, row, column);
|
||||
$tr.append(td);
|
||||
});
|
||||
|
||||
return $tr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an element with the value of a field
|
||||
*/
|
||||
function _displayField(el, row, field, truncate) {
|
||||
var val = _getValForField(row, field, truncate);
|
||||
if (val instanceof DOMNode) {
|
||||
el.appendChild(val);
|
||||
} else {
|
||||
el.textContent = val;
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value of a field from a row, serialize it to a string
|
||||
* and truncate it if necessary
|
||||
*
|
||||
* @param {object} row - the row to pull the value from
|
||||
* @param {string} field - the name of the field (dot-seperated paths are accepted)
|
||||
* @param {boolean} untruncate - Should truncated values have a "more" link to expand the text?
|
||||
* @return {[type]} a string, which should be inserted as text, or an element
|
||||
*/
|
||||
function _getValForField(row, field, untruncate) {
|
||||
var val;
|
||||
|
||||
// is field name a path?
|
||||
if (~field.indexOf('.')) {
|
||||
var path = field.split('.');
|
||||
// only check source for "paths"
|
||||
var current = row._source;
|
||||
var step;
|
||||
while (step = path.shift() && current) {
|
||||
// walk from the _source to the specified by the path
|
||||
current = current[step];
|
||||
}
|
||||
val = current;
|
||||
} else {
|
||||
// simple, with a fallback to row
|
||||
val = row._source[field] || row[field];
|
||||
}
|
||||
|
||||
// undefined and null should just be an empty string
|
||||
val = (val == null) ? '' : val;
|
||||
|
||||
// stringify array's and objects
|
||||
if (typeof val === 'object') val = JSON.stringify(val);
|
||||
|
||||
// truncate
|
||||
if (typeof val === 'string' && val.length > $scope.maxLength) {
|
||||
if (untruncate) {
|
||||
var complete = val;
|
||||
val = document.createElement('kbn-truncated');
|
||||
val.setAttribute('orig', complete);
|
||||
val.setAttribute('length', $scope.maxLength);
|
||||
val = $compile(val)($scope)[0];// return the actual element
|
||||
} else {
|
||||
val = val.substring(0, $scope.maxLength) + '...';
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
40
src/kibana/directives/truncated.js
Normal file
40
src/kibana/directives/truncated.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
define(function (require) {
|
||||
var module = require('angular').module('kibana/directives');
|
||||
var $ = require('jquery');
|
||||
|
||||
module.directive('kbnTruncated', function ($compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
orig: '@',
|
||||
length: '@'
|
||||
},
|
||||
template: function ($element, attrs) {
|
||||
var template = '<span>{{text}}</span>';
|
||||
if (attrs.length && attrs.orig && attrs.orig.length > attrs.length) {
|
||||
template += ' <a ng-click="toggle($event)">{{action}}</a>';
|
||||
}
|
||||
return template;
|
||||
},
|
||||
link: function ($scope, $element, attrs) {
|
||||
var fullText = $scope.orig;
|
||||
var truncated = fullText.substring(0, $scope.length);
|
||||
|
||||
if (fullText === truncated) return;
|
||||
|
||||
truncated += '...';
|
||||
|
||||
$scope.expanded = false;
|
||||
$scope.text = truncated;
|
||||
$scope.action = 'more';
|
||||
|
||||
$scope.toggle = function ($event) {
|
||||
$event.stopPropagation();
|
||||
$scope.expanded = !$scope.expanded;
|
||||
$scope.text = $scope.expanded ? fullText : truncated;
|
||||
$scope.action = $scope.expanded ? 'less' : 'more';
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -3,4 +3,5 @@
|
|||
<th ng-repeat="name in columns">{{name}}</th>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</table>
|
||||
<kbn-infinite-scroll more="addRows"></kbn-infinite-scroll>
|
69
src/kibana/services/visualization.js
Normal file
69
src/kibana/services/visualization.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
|
||||
var module = angular.module('kibana/services');
|
||||
|
||||
module.service('visualizations', function (courier, es, config, visFactory, $q) {
|
||||
this.getOrCreate = function (reject, resolve, id) {
|
||||
if (!id) return this.create(id);
|
||||
|
||||
return this.get(id)
|
||||
.catch(function (err) {
|
||||
return this.create(id);
|
||||
});
|
||||
};
|
||||
|
||||
this.get = function (id) {
|
||||
var defer = $q.defer();
|
||||
|
||||
var settingSource = courier.createSource('doc')
|
||||
.index(config.get('visualizations.index'))
|
||||
.type(config.get('visualizations.type'))
|
||||
.id(id)
|
||||
.on('update', function onResult(doc) {
|
||||
if (doc.found) {
|
||||
// the source will re-emit it's most recent result
|
||||
// once "results" is listened for
|
||||
defer.resolve(visFactory(settingSource));
|
||||
} else {
|
||||
defer.reject(new Error('Doc not found'));
|
||||
}
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
this.create = function (reject, resolve) {
|
||||
var defer = $q.defer();
|
||||
|
||||
var docSource = courier.createSource('doc')
|
||||
.index(config.get('visualizations.index'))
|
||||
.type(config.get('visualizations.type'));
|
||||
|
||||
defer.resolve(visFactory(docSource));
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
this.list = function (reject, resolve) {
|
||||
return es.search({
|
||||
index: config.get('visualizations.index'),
|
||||
type: config.get('visualizations.type'),
|
||||
body: {
|
||||
query: {
|
||||
match_all: {}
|
||||
}
|
||||
}
|
||||
}).then(function (resp) {
|
||||
return _.map(resp.hits.hits, function (hit) {
|
||||
return {
|
||||
name: hit._source.title,
|
||||
id: hit._id,
|
||||
type: hit._source.type
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
});
|
18
src/kibana/styles/_table.less
Normal file
18
src/kibana/styles/_table.less
Normal file
|
@ -0,0 +1,18 @@
|
|||
kbn-table {
|
||||
// sub tables should not have a leading border
|
||||
.table .table {
|
||||
margin-bottom: 0px;
|
||||
|
||||
tr:first-child > td {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
td.field-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
tr.even td {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
}
|
|
@ -6986,3 +6986,20 @@ body {
|
|||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
|
||||
text-align: center;
|
||||
}
|
||||
kbn-table .table .table {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
kbn-table .table .table tr:first-child > td {
|
||||
border-top: none;
|
||||
}
|
||||
kbn-table .table .table td.field-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
kbn-table tr.even td {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
disc-field-chooser ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
|
|
@ -49,4 +49,7 @@ body {
|
|||
@shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);
|
||||
.box-shadow(@shadow);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@import "./_table.less";
|
||||
@import "../apps/discover/styles/main.less";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue