mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
started removing kibana stuff I do not need, thinking of doing a custom dashboard based on kibana
This commit is contained in:
parent
d1c54c1c78
commit
50e42c8bdd
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
.aws-config.json
|
||||
dist
|
||||
dist
|
||||
web.config
|
@ -1,19 +0,0 @@
|
||||
If you have a bugfix or new feature that you would like to contribute to Kibana, please **find or open an issue about it before you start working on it.** Talk about what you would like to do. It may be that somebody is already working on it, or that there are particular issues that you should know about before implementing the change.
|
||||
|
||||
We enjoy working with contributors to get their code accepted. There are many approaches to fixing a problem and it is important to find the best approach before writing too much code.
|
||||
|
||||
The process for contributing to any of the Elasticsearch repositories is similar.
|
||||
|
||||
1. Sign the contributor license agreement
|
||||
Please make sure you have signed the [Contributor License Agreement](http://www.elasticsearch.org/contributor-agreement/). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once.
|
||||
|
||||
2. Run the grunt build process and ensure it completes without errors with your changes.
|
||||
|
||||
3. Rebase your changes
|
||||
Update your local repository with the most recent code from the main Kibana repository, and rebase your branch on top of the latest master branch. We prefer your changes to be squashed into a single commit.
|
||||
|
||||
4. Submit a pull request
|
||||
Push your local changes to your forked copy of the repository and submit a pull request. In the pull request, describe what your changes do and mention the number of the issue where discussion has taken place, eg “Closes #123″.
|
||||
|
||||
Then sit back and wait. There will probably be discussion about the pull request and, if any changes are needed, we would love to work with you to get your pull request merged into Kibana.
|
||||
|
51
README.md
51
README.md
@ -1,51 +1,4 @@
|
||||
# Kibana
|
||||
# Grafana
|
||||
|
||||
__NOTE__: You have reached the Kibana 3 repository.
|
||||
Kibana 3 is a completely new version of Kibana written entirely in HTML and Javascript. You can find
|
||||
the Kibana 2 repository at [https://github.com/rashidkpc/Kibana](https://github.com/rashidkpc/Kibana)
|
||||
Experimenting with a a custom graph dashboard based on [kibana](https://github.com/elasticsearch/kibana).
|
||||
|
||||
More information about Kibana 3 can be found at [http://www.elasticsearch.org/overview/kibana/](http://www.elasticsearch.org/overview/kibana/)
|
||||
|
||||
## Overview
|
||||
|
||||
Kibana is an open source (Apache Licensed), browser based analytics and search interface to Logstash
|
||||
and other timestamped data sets stored in ElasticSearch. With those in place Kibana is a snap to
|
||||
setup and start using (seriously). Kibana strives to be easy to get started with, while also being
|
||||
flexible and powerful
|
||||
|
||||
### Requirements
|
||||
* A modern web browser. The latest version of Chrome, Safari and Firefox have all been tested to
|
||||
work. IE9 and greater should work. IE8 does not.
|
||||
* A webserver. No extensions are required, as long as it can serve plain html it will work
|
||||
* A browser reachable Elasticsearch server. Port 9200 must be open, or a proxy configured to allow
|
||||
access to it.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Download and extract [http://download.elasticsearch.org/kibana/kibana/kibana-latest.zip](http://download.elasticsearch.org/kibana/kibana/kibana-latest.zip) to your webserver.
|
||||
2. Edit config.js in your deployed directory to point to your elasticsearch server. This should __not be
|
||||
http://localhost:9200__, but rather the fully qualified domain name of your elasticsearch server.
|
||||
The url entered here _must be reachable_ by your browser.
|
||||
3. Point your browser at your installation. If you're using Logstash with the default indexing
|
||||
configuration the included Kibana logstash interface should work nicely.
|
||||
|
||||
### FAQ
|
||||
__Q__: Why doesnt it work? I have http://localhost:9200 in my config.js, my webserver and elasticsearch
|
||||
server are on the same machine
|
||||
__A__: Kibana 3 does not work like previous versions of Kibana. To ease deployment, the server side
|
||||
component has been eliminated. Thus __the browser connects directly to Elasticsearch__. The default
|
||||
config.js setup works for the webserver+Elasticsearch on the same machine scenario. Do not set it
|
||||
to http://localhost:9200 unless your browser and elasticsearch are on the same machine
|
||||
|
||||
__Q__: How do I secure this? I don't want to leave 9200 open.
|
||||
__A__: A simple nginx virtual host and proxy configuration can be found in the sample/nginx.conf
|
||||
|
||||
### Support
|
||||
|
||||
If you have questions or comments the best place to reach me is #logstash or #elasticsearch on irc.freenode.net
|
||||
|
||||
### Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md](https://github.com/elasticsearch/kibana/blob/master/CONTRIBUTING.md).
|
||||
If you have a bugfix or new feature that you would like to contribute to Kibana, **please find or open an issue
|
||||
about it first.**
|
||||
|
@ -1,29 +0,0 @@
|
||||
== Configuration ==
|
||||
config.js is where you will find the core Kibana configuration. This file contains parameter that
|
||||
must be set before kibana is run for the first time.
|
||||
// src/config.js:1
|
||||
|
||||
=== Parameters ===
|
||||
// src/config.js:10
|
||||
|
||||
==== elasticsearch ====
|
||||
|
||||
The URL to your elasticsearch server. You almost certainly don't
|
||||
want +http://localhost:9200+ here. Even if Kibana and Elasticsearch are on
|
||||
the same host. By default this will attempt to reach ES at the same host you have
|
||||
elasticsearch installed on. You probably want to set it to the FQDN of your
|
||||
elasticsearch host
|
||||
// src/config.js:15
|
||||
|
||||
==== kibana-int ====
|
||||
|
||||
The default ES index to use for storing Kibana specific object
|
||||
such as stored dashboards
|
||||
// src/config.js:26
|
||||
|
||||
==== panel_name ====
|
||||
|
||||
An array of panel modules available. Panels will only be loaded when they are defined in the
|
||||
dashboard, but this list is used in the "add panel" interface.
|
||||
// src/config.js:34
|
||||
|
@ -1,18 +0,0 @@
|
||||
= Kibana
|
||||
|
||||
// Why can't I have a preamble here?
|
||||
|
||||
== Introduction
|
||||
|
||||
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for
|
||||
ElasticSearch. Kibana is a snap to setup and start using. Written entirely in HTML and Javascript
|
||||
it requires only a plain webserver, Kibana requires no fancy server side components.
|
||||
Kibana strives to be easy to get started with, while also being flexible and powerful, just like
|
||||
Elasticsearch.
|
||||
|
||||
include::configuration/config.js.asciidoc[]
|
||||
|
||||
include::panels.asciidoc[]
|
||||
|
||||
// src/app/controllers/dash.js:1
|
||||
|
@ -1,64 +0,0 @@
|
||||
[[panels]]
|
||||
= Panels
|
||||
|
||||
[partintro]
|
||||
--
|
||||
*Kibana* dashboards are made up of blocks called +panels+. Panels are organized into rows
|
||||
and can serve many purposes, though most are designed to provide the results of a query or
|
||||
multiple queries as a visualization. Other panels may show collections of documents or
|
||||
allow you to insert instructions for your users.
|
||||
|
||||
Panels can be configured easily via the Kibana web interface. For more advanced usage, such
|
||||
as templated or scripted dashboards, documentation of panel properties is available in this
|
||||
section. You may find settings here which are not exposed via the web interface.
|
||||
|
||||
Each panel type has its own properties, hover there are several that are shared.
|
||||
|
||||
// src/app/controllers/row.js:61
|
||||
|
||||
span:: A number, 1-12, that describes the width of the panel.
|
||||
// src/app/controllers/row.js:87
|
||||
|
||||
editable:: Enable or disable the edit button the the panel
|
||||
// src/app/controllers/row.js:91
|
||||
|
||||
type:: The type of panel this object contains. Each panel type will require additional
|
||||
properties. See the panel types list to the right.
|
||||
// src/app/controllers/row.js:95
|
||||
|
||||
--
|
||||
// src/app/controllers/row.js:103
|
||||
|
||||
include::panels/bettermap.asciidoc[]
|
||||
// src/app/panels/bettermap/module.js:1
|
||||
|
||||
include::panels/column.asciidoc[]
|
||||
// src/app/panels/column/module.js:1
|
||||
|
||||
include::panels/histogram.asciidoc[]
|
||||
// src/app/panels/histogram/module.js:1
|
||||
|
||||
include::panels/hits.asciidoc[]
|
||||
// src/app/panels/hits/module.js:1
|
||||
|
||||
include::panels/map.asciidoc[]
|
||||
// src/app/panels/map/module.js:1
|
||||
|
||||
include::panels/pie.asciidoc[]
|
||||
// src/app/panels/pie/module.js:1
|
||||
|
||||
include::panels/sparklines.asciidoc[]
|
||||
// src/app/panels/sparklines/module.js:1
|
||||
|
||||
include::panels/table.asciidoc[]
|
||||
// src/app/panels/table/module.js:1
|
||||
|
||||
include::panels/terms.asciidoc[]
|
||||
// src/app/panels/terms/module.js:1
|
||||
|
||||
include::panels/text.asciidoc[]
|
||||
// src/app/panels/text/module.js:1
|
||||
|
||||
include::panels/trends.asciidoc[]
|
||||
// src/app/panels/trends/module.js:1
|
||||
|
@ -1,36 +0,0 @@
|
||||
== Bettermap
|
||||
Status: *Experimental*
|
||||
|
||||
Bettermap is called bettermap for lack of a better name. Bettermap uses geographic coordinates to
|
||||
create clusters of markers on map and shade them orange, yellow and green depending on the
|
||||
density of the cluster.
|
||||
|
||||
To drill down, click on a cluster. The map will be zoomed and the cluster broken into smaller cluster.
|
||||
When it no longer makes visual sense to cluster, individual markers will be displayed. Hover over
|
||||
a marker to see the tooltip value/
|
||||
|
||||
IMPORTANT: bettermap requires an internet connection to download its map panels.
|
||||
// src/app/panels/bettermap/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
field:: The field that contains the coordinates, in geojson format. GeoJSON is
|
||||
+[longitude,latitude]+ in an array. This is different from most implementations, which use
|
||||
latitude, longitude.
|
||||
// src/app/panels/bettermap/module.js:62
|
||||
|
||||
size:: The number of documents to use when drawing the map
|
||||
// src/app/panels/bettermap/module.js:70
|
||||
|
||||
spyable:: Should the `inspect` icon be shown?
|
||||
// src/app/panels/bettermap/module.js:74
|
||||
|
||||
tooltip:: Which field to use for the tooltip when hovering over a marker
|
||||
// src/app/panels/bettermap/module.js:78
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/bettermap/module.js:82
|
||||
|
@ -1,14 +0,0 @@
|
||||
== Column
|
||||
Status: *Stable*
|
||||
|
||||
A pseudo panel that lets you add other panels to be arranged in a column with defined heights.
|
||||
While the column panel is stable, it does have many limitations, including the inability to drag
|
||||
and drop panels within its borders. It may be removed in a future release.
|
||||
|
||||
// src/app/panels/column/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
panel:: An array of panel objects
|
||||
// src/app/panels/column/module.js:36
|
||||
|
@ -1,129 +0,0 @@
|
||||
== Histogram
|
||||
Status: *Stable*
|
||||
|
||||
The histogram panel allow for the display of time charts. It includes several modes and tranformations
|
||||
to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
|
||||
fields.
|
||||
|
||||
// src/app/panels/histogram/module.js:5
|
||||
|
||||
=== Parameters
|
||||
==== Axis options
|
||||
mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
|
||||
defined. Possible values: count, mean, max, min, total.
|
||||
// src/app/panels/histogram/module.js:65
|
||||
|
||||
time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
|
||||
// src/app/panels/histogram/module.js:72
|
||||
|
||||
value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
|
||||
// src/app/panels/histogram/module.js:76
|
||||
|
||||
x-axis:: Show the x-axis
|
||||
// src/app/panels/histogram/module.js:80
|
||||
|
||||
y-axis:: Show the y-axis
|
||||
// src/app/panels/histogram/module.js:84
|
||||
|
||||
scale:: Scale the y-axis by this factor
|
||||
// src/app/panels/histogram/module.js:88
|
||||
|
||||
y_format:: 'none','bytes','short '
|
||||
// src/app/panels/histogram/module.js:92
|
||||
|
||||
==== Annotations
|
||||
annotate object:: A query can be specified, the results of which will be displayed as markers on
|
||||
the chart. For example, for noting code deploys.
|
||||
annotate.enable::: Should annotations, aka markers, be shown?
|
||||
annotate.query::: Lucene query_string syntax query to use for markers.
|
||||
annotate.size::: Max number of markers to show
|
||||
annotate.field::: Field from documents to show
|
||||
annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
|
||||
// src/app/panels/histogram/module.js:115
|
||||
|
||||
==== Interval options
|
||||
auto_int:: Automatically scale intervals?
|
||||
// src/app/panels/histogram/module.js:132
|
||||
|
||||
resolution:: If auto_int is true, shoot for this many bars.
|
||||
// src/app/panels/histogram/module.js:137
|
||||
|
||||
interval:: If auto_int is set to false, use this as the interval.
|
||||
// src/app/panels/histogram/module.js:141
|
||||
|
||||
interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
|
||||
// src/app/panels/histogram/module.js:145
|
||||
|
||||
==== Drawing options
|
||||
lines:: Show line chart
|
||||
// src/app/panels/histogram/module.js:149
|
||||
|
||||
fill:: Area fill factor for line charts, 1-10
|
||||
// src/app/panels/histogram/module.js:154
|
||||
|
||||
linewidth:: Weight of lines in pixels
|
||||
// src/app/panels/histogram/module.js:158
|
||||
|
||||
points:: Show points on chart
|
||||
// src/app/panels/histogram/module.js:162
|
||||
|
||||
pointradius:: Size of points in pixels
|
||||
// src/app/panels/histogram/module.js:166
|
||||
|
||||
bars:: Show bars on chart
|
||||
// src/app/panels/histogram/module.js:170
|
||||
|
||||
stack:: Stack multiple series
|
||||
// src/app/panels/histogram/module.js:174
|
||||
|
||||
spyable:: Show inspect icon
|
||||
// src/app/panels/histogram/module.js:178
|
||||
|
||||
zoomlinks:: Show `Zoom Out' link
|
||||
// src/app/panels/histogram/module.js:182
|
||||
|
||||
options:: Show quick view options section
|
||||
// src/app/panels/histogram/module.js:186
|
||||
|
||||
legend:: Display the legond
|
||||
// src/app/panels/histogram/module.js:190
|
||||
|
||||
show_query:: If no alias is set, should the query be displayed?
|
||||
// src/app/panels/histogram/module.js:194
|
||||
|
||||
interactive:: Enable click-and-drag to zoom functionality
|
||||
// src/app/panels/histogram/module.js:198
|
||||
|
||||
legend_counts:: Show counts in legend
|
||||
// src/app/panels/histogram/module.js:202
|
||||
|
||||
==== Transformations
|
||||
timezone:: Correct for browser timezone?. Valid values: browser, utc
|
||||
// src/app/panels/histogram/module.js:206
|
||||
|
||||
percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
|
||||
queries
|
||||
// src/app/panels/histogram/module.js:211
|
||||
|
||||
zerofill:: Improves the accuracy of line charts at a small performance cost.
|
||||
// src/app/panels/histogram/module.js:216
|
||||
|
||||
derivative:: Show each point on the x-axis as the change from the previous point
|
||||
// src/app/panels/histogram/module.js:220
|
||||
|
||||
tooltip object::
|
||||
tooltip.value_type::: Individual or cumulative controls how tooltips are display on stacked charts
|
||||
tooltip.query_as_alias::: If no alias is set, should the query be displayed?
|
||||
// src/app/panels/histogram/module.js:224
|
||||
|
||||
grid object:: Min and max y-axis values
|
||||
grid.min::: Minimum y-axis value
|
||||
grid.max::: Maximum y-axis value
|
||||
// src/app/panels/histogram/module.js:96
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/histogram/module.js:105
|
||||
|
@ -1,37 +0,0 @@
|
||||
== Hits
|
||||
Status: *Stable*
|
||||
|
||||
The hits panel displays the number of hits for each of the queries on the dashboard in a
|
||||
configurable format specified by the `chart' property.
|
||||
|
||||
// src/app/panels/hits/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
arrangement:: The arrangement of the legend. horizontal or vertical
|
||||
// src/app/panels/hits/module.js:49
|
||||
|
||||
chart:: bar, pie or none
|
||||
// src/app/panels/hits/module.js:55
|
||||
|
||||
counter_pos:: The position of the legend, above or below
|
||||
// src/app/panels/hits/module.js:59
|
||||
|
||||
donut:: If the chart is set to pie, setting donut to true will draw a hole in the midle of it
|
||||
// src/app/panels/hits/module.js:63
|
||||
|
||||
tilt:: If the chart is set to pie, setting tilt to true will tilt it back into an oval
|
||||
// src/app/panels/hits/module.js:67
|
||||
|
||||
labels:: If the chart is set to pie, setting labels to true will draw labels in the slices
|
||||
// src/app/panels/hits/module.js:71
|
||||
|
||||
spyable:: Setting spyable to false disables the inspect icon.
|
||||
// src/app/panels/hits/module.js:75
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/hits/module.js:79
|
||||
|
@ -1,32 +0,0 @@
|
||||
== Map
|
||||
Status: *Stable*
|
||||
|
||||
The map panel translates 2 letter country or state codes into shaded regions on a map. Currently
|
||||
available maps are world, usa and europe.
|
||||
|
||||
// src/app/panels/map/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
map:: Map to display. world, usa, europe
|
||||
// src/app/panels/map/module.js:48
|
||||
|
||||
colors:: An array of colors to use to shade the map. If 2 colors are specified, shades
|
||||
between them will be used. For example [`#A0E2E2', `#265656']
|
||||
// src/app/panels/map/module.js:54
|
||||
|
||||
size:: Max number of regions to shade
|
||||
// src/app/panels/map/module.js:59
|
||||
|
||||
exclude:: exclude this array of regions. For example [`US',`BR',`IN']
|
||||
// src/app/panels/map/module.js:63
|
||||
|
||||
spyable:: Setting spyable to false disables the inspect icon.
|
||||
// src/app/panels/map/module.js:67
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/map/module.js:71
|
||||
|
@ -1,50 +0,0 @@
|
||||
== Pie
|
||||
Status: *Deprecated*
|
||||
|
||||
The pie panel has been largely replaced by the +terms+ panel. It exists for backwards compatibility
|
||||
for now, but will be removed in a future release
|
||||
|
||||
// src/app/panels/pie/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
mode:: terms or goal. Terms mode finds the top N most popular terms, Goal mode display
|
||||
progress towards a fix goal in terms of documents matched
|
||||
// src/app/panels/pie/module.js:48
|
||||
|
||||
size:: The max number of results to display in +terms+ mode.
|
||||
// src/app/panels/pie/module.js:55
|
||||
|
||||
exclude:: Exclude these terms in terms mode
|
||||
// src/app/panels/pie/module.js:59
|
||||
|
||||
donut:: Draw a hole in the middle of the pie, creating a tasty donut.
|
||||
// src/app/panels/pie/module.js:63
|
||||
|
||||
tilt:: Tilt the pie back into an oval shape
|
||||
// src/app/panels/pie/module.js:67
|
||||
|
||||
legend:: The location of the legend, above, below or none
|
||||
// src/app/panels/pie/module.js:71
|
||||
|
||||
labels:: Set to false to disable drawing labels inside the pie slices
|
||||
// src/app/panels/pie/module.js:75
|
||||
|
||||
spyable:: Set to false to disable the inspect function.
|
||||
// src/app/panels/pie/module.js:79
|
||||
|
||||
==== Query
|
||||
|
||||
query object:: This confusingly named object has properties to set the terms mode field,
|
||||
and the fixed goal for the goal mode
|
||||
query.field::: the field to facet on in terms mode
|
||||
query.goal::: the fixed goal for goal mode
|
||||
// src/app/panels/pie/module.js:83
|
||||
|
||||
==== Queries
|
||||
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/pie/module.js:92
|
||||
|
@ -1,32 +0,0 @@
|
||||
== Sparklines
|
||||
Status: *Experimental*
|
||||
|
||||
The sparklines panel shows tiny time charts. The purpose of these is not to give an exact value,
|
||||
but rather to show the shape of the time series in a compact manner
|
||||
|
||||
// src/app/panels/sparklines/module.js:5
|
||||
|
||||
=== Parameters
|
||||
mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
|
||||
defined. Possible values: count, mean, max, min, total.
|
||||
// src/app/panels/sparklines/module.js:56
|
||||
|
||||
time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
|
||||
// src/app/panels/sparklines/module.js:62
|
||||
|
||||
value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
|
||||
// src/app/panels/sparklines/module.js:66
|
||||
|
||||
interval:: Sparkline intervals are computed automatically as long as there is a time filter
|
||||
present. In the absence of a time filter, use this interval.
|
||||
// src/app/panels/sparklines/module.js:70
|
||||
|
||||
spyable:: Show inspect icon
|
||||
// src/app/panels/sparklines/module.js:75
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/sparklines/module.js:79
|
||||
|
@ -1,69 +0,0 @@
|
||||
== table
|
||||
Status: *Stable*
|
||||
|
||||
The table panel contains a sortable, pagable view of documents that. It can be arranged into
|
||||
defined columns and offers several interactions, such as performing adhoc terms aggregations.
|
||||
|
||||
// src/app/panels/table/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
size:: The number of hits to show per page
|
||||
// src/app/panels/table/module.js:53
|
||||
|
||||
pages:: The number of pages available
|
||||
// src/app/panels/table/module.js:59
|
||||
|
||||
offset:: The current page
|
||||
// src/app/panels/table/module.js:63
|
||||
|
||||
sort:: An array describing the sort order of the table. For example [`@timestamp',`desc']
|
||||
// src/app/panels/table/module.js:67
|
||||
|
||||
overflow:: The css overflow property. `min-height' (expand) or `auto' (scroll)
|
||||
// src/app/panels/table/module.js:71
|
||||
|
||||
fields:: the fields used a columns of the table, in an array.
|
||||
// src/app/panels/table/module.js:75
|
||||
|
||||
highlight:: The fields on which to highlight, in an array
|
||||
// src/app/panels/table/module.js:79
|
||||
|
||||
sortable:: Set sortable to false to disable sorting
|
||||
// src/app/panels/table/module.js:83
|
||||
|
||||
header:: Set to false to hide the table column names
|
||||
// src/app/panels/table/module.js:87
|
||||
|
||||
paging:: Set to false to hide the paging controls of the table
|
||||
// src/app/panels/table/module.js:91
|
||||
|
||||
field_list:: Set to false to hide the list of fields. The user will be able to expand it,
|
||||
but it will be hidden by default
|
||||
// src/app/panels/table/module.js:95
|
||||
|
||||
all_fields:: Set to true to show all fields in the mapping, not just the current fields in
|
||||
the table.
|
||||
// src/app/panels/table/module.js:100
|
||||
|
||||
trimFactor:: The trim factor is the length at which to truncate fields takinging into
|
||||
consideration the number of columns in the table. For example, a trimFactor of 100, with 5
|
||||
columns in the table, would trim each column at 20 character. The entirety of the field is
|
||||
still available in the expanded view of the event.
|
||||
// src/app/panels/table/module.js:105
|
||||
|
||||
localTime:: Set to true to adjust the timeField to the browser's local time
|
||||
// src/app/panels/table/module.js:112
|
||||
|
||||
timeField:: If localTime is set to true, this field will be adjusted to the browsers local time
|
||||
// src/app/panels/table/module.js:116
|
||||
|
||||
spyable:: Set to false to disable the inspect icon
|
||||
// src/app/panels/table/module.js:120
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/table/module.js:124
|
||||
|
@ -1,56 +0,0 @@
|
||||
== terms
|
||||
Status: *Stable*
|
||||
|
||||
A table, bar chart or pie chart based on the results of an Elasticsearch terms facet.
|
||||
|
||||
// src/app/panels/terms/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
field:: The field on which to computer the facet
|
||||
// src/app/panels/terms/module.js:45
|
||||
|
||||
exclude:: terms to exclude from the results
|
||||
// src/app/panels/terms/module.js:51
|
||||
|
||||
missing:: Set to false to disable the display of a counter showing how much results are
|
||||
missing the field
|
||||
// src/app/panels/terms/module.js:55
|
||||
|
||||
other:: Set to false to disable the display of a counter representing the aggregate of all
|
||||
values outside of the scope of your +size+ property
|
||||
// src/app/panels/terms/module.js:60
|
||||
|
||||
size:: Show this many terms
|
||||
// src/app/panels/terms/module.js:65
|
||||
|
||||
order:: count, term, reverse_count or reverse_term
|
||||
// src/app/panels/terms/module.js:69
|
||||
|
||||
donut:: In pie chart mode, draw a hole in the middle of the pie to make a tasty donut.
|
||||
// src/app/panels/terms/module.js:74
|
||||
|
||||
tilt:: In pie chart mode, tilt the chart back to appear as more of an oval shape
|
||||
// src/app/panels/terms/module.js:78
|
||||
|
||||
lables:: In pie chart mode, draw labels in the pie slices
|
||||
// src/app/panels/terms/module.js:82
|
||||
|
||||
arrangement:: In bar or pie mode, arrangement of the legend. horizontal or vertical
|
||||
// src/app/panels/terms/module.js:86
|
||||
|
||||
chart:: table, bar or pie
|
||||
// src/app/panels/terms/module.js:90
|
||||
|
||||
counter_pos:: The location of the legend in respect to the chart, above or below.
|
||||
// src/app/panels/terms/module.js:94
|
||||
|
||||
spyable:: Set spyable to false to disable the inspect button
|
||||
// src/app/panels/terms/module.js:98
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/terms/module.js:102
|
||||
|
@ -1,16 +0,0 @@
|
||||
== text
|
||||
Status: *Stable*
|
||||
|
||||
The text panel is used for displaying static text formated as markdown, sanitized html or as plain
|
||||
text.
|
||||
|
||||
// src/app/panels/text/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
mode:: `html', `markdown' or `text'
|
||||
// src/app/panels/text/module.js:33
|
||||
|
||||
content:: The content of your panel, written in the mark up specified in +mode+
|
||||
// src/app/panels/text/module.js:39
|
||||
|
@ -1,27 +0,0 @@
|
||||
== trends
|
||||
Status: *Beta*
|
||||
|
||||
A stock-ticker style representation of how queries are moving over time. For example, if the
|
||||
time is 1:10pm, your time picker was set to "Last 10m", and the "Time Ago" parameter was set to
|
||||
"1h", the panel would show how much the query results have changed since 12:00-12:10pm
|
||||
|
||||
// src/app/panels/trends/module.js:5
|
||||
|
||||
=== Parameters
|
||||
|
||||
ago:: A date math formatted string describing the relative time period to compare the
|
||||
queries to.
|
||||
// src/app/panels/trends/module.js:49
|
||||
|
||||
arrangement:: `horizontal' or `vertical'
|
||||
// src/app/panels/trends/module.js:56
|
||||
|
||||
spyable:: Set to false to disable the inspect icon
|
||||
// src/app/panels/trends/module.js:60
|
||||
|
||||
==== Queries
|
||||
queries object:: This object describes the queries to use on this panel.
|
||||
queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
// src/app/panels/trends/module.js:64
|
||||
|
@ -12,7 +12,6 @@
|
||||
"devDependencies": {
|
||||
"rjs-build-analysis": "0.0.3",
|
||||
"grunt": "~0.4.0",
|
||||
"grunt-s3": "~0.2.0-alpha.2",
|
||||
"grunt-ngmin": "0.0.3",
|
||||
"grunt-contrib": "~0.8.0",
|
||||
"grunt-contrib-less": "~0.7.0",
|
||||
@ -29,8 +28,7 @@
|
||||
"grunt-contrib-uglify": "~0.2.4",
|
||||
"load-grunt-tasks": "~0.2.0",
|
||||
"glob": "~3.2.7",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-scratchy": "git://github.com/rashidkpc/grunt-scratchy.git"
|
||||
"grunt-contrib-connect": "~0.5.0"
|
||||
},
|
||||
"license": "Apache License"
|
||||
}
|
||||
|
@ -24,8 +24,8 @@
|
||||
"rows": [
|
||||
{
|
||||
"title": "Intro",
|
||||
"height": "450px",
|
||||
"editable": false,
|
||||
"height": "150px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": false,
|
||||
"panels": [
|
||||
@ -38,7 +38,7 @@
|
||||
],
|
||||
"type": "text",
|
||||
"mode": "markdown",
|
||||
"content": " \n\n##### Did you just upgrade? Not expecting this screen?\nIf you were using the old default page you might not be expecting this screen. I understand, change can be awkward. Let me explain. \n\n##### Setting a global default dashboard\nKibana has always shipped with an interface for Logstash, still does! You can access it [here](index.html#dashboard/file/logstash.json). However, if you want to make it your default again, all you need to do is rename a file!\nIn your Kibana installation directory: \n\nRename *logstash.json* to *default.json* and refresh. Should be all set.\n\n##### But wait, there's more!\nIn fact, you can add any exported dashboard to that directory and access it as *http://YOUR-HOST -HERE/index.html#dashboard/file/YOUR-DASHBOARD.json*. Neat trick eh?",
|
||||
"content": "hej!",
|
||||
"style": {},
|
||||
"title": "",
|
||||
"status": "Stable"
|
||||
@ -46,21 +46,17 @@
|
||||
{
|
||||
"error": false,
|
||||
"span": 8,
|
||||
"editable": false,
|
||||
"editable": true,
|
||||
"group": [
|
||||
"default"
|
||||
],
|
||||
"type": "text",
|
||||
"mode": "markdown",
|
||||
"content": "### Welcome to Kibana. \nGlad you could make it. Happy to have you here! Lets get started, shall we?\n##### Requirements\n* **A good browser.** \n The latest version of Chrome or Firefox is recommended. Safari (latest version) and Internet Explorer 9 and above are also supported.\n* **A webserver.** \n Just somewhere to host the HTML and Javascript. Basically any webserver will work.\n* **Elasticsearch** \n 0.20.5 or above. Kibana will soon move to requiring Elasticsearch 0.90 or above, so upgrading is recommended.\n\n##### Configuration\nIf Kibana and Elasticsearch are on the same host, and you're using the default Elasticsearch port, then you're all set. Kibana is configured to use that setup by default! \n\nIf not, you need to edit *config.js* and set the *elasticsearch* parameter with the URL (including port, probably 9200) of your Elasticsearch server. The host part should be the entire, fully qualified domain name, or IP, **not localhost**.\n#### Are you a Logstash User?\n+ **YES** - Great! We have a prebuilt dashboard: [(Logstash Dashboard)](index.html#/dashboard/file/logstash.json). See the note to the right about making it your global default \n\n+ **NO** - Hey, no problem, you just have a bit of setup to do. You have a few choices: \n\n 1. [Sample Dashboard](index.html#/dashboard/file/guided.json) *I don't have much data yet, please extract some basics for me* \n 2. [Unconfigured Dashboard](index.html#/dashboard/file/noted.json) *I have a lot of data and I don't want Kibana to query it at once*\n 3. [Blank Dashboard](index.html#/dashboard/file/blank.json) *I'm comfortable figuring it out on my own*",
|
||||
"style": {},
|
||||
"status": "Stable"
|
||||
"type": "graph"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
"notice": true
|
||||
}
|
||||
],
|
||||
"editable": false,
|
||||
"editable": true,
|
||||
"index": {
|
||||
"interval": "none",
|
||||
"pattern": "[logstash-]YYYY.MM.DD",
|
||||
@ -71,7 +67,38 @@
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"pulldowns": [],
|
||||
"nav": [],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
|
@ -1,17 +0,0 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span4">
|
||||
<form>
|
||||
<h6>Coordinate Field <tip>geoJSON array! Long,Lat NOT Lat,Long</tip></h6>
|
||||
<input bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.field">
|
||||
</form>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<form>
|
||||
<h6>Tooltip Field</h6>
|
||||
<input bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.tooltip">
|
||||
</form>
|
||||
</div>
|
||||
<div class="span2"><h6>Max Points</h6>
|
||||
<input type="number" class="input-small" ng-model="panel.size">
|
||||
</div>
|
||||
</div>
|
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 797 B |
File diff suppressed because it is too large
Load Diff
@ -1,463 +0,0 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-map-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-pane,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-overlay-pane,
|
||||
.leaflet-shadow-pane,
|
||||
.leaflet-marker-pane,
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-overlay-pane svg,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
-ms-touch-action: none;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container img {
|
||||
max-width: none !important;
|
||||
}
|
||||
/* stupid Android 2 doesn't understand "max-width: none" properly */
|
||||
.leaflet-container img.leaflet-image-layer {
|
||||
max-width: 15000px !important;
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.leaflet-tile-pane { z-index: 2; }
|
||||
.leaflet-objects-pane { z-index: 3; }
|
||||
.leaflet-overlay-pane { z-index: 4; }
|
||||
.leaflet-shadow-pane { z-index: 5; }
|
||||
.leaflet-marker-pane { z-index: 6; }
|
||||
.leaflet-popup-pane { z-index: 7; }
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-tile,
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-tile-loaded,
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile,
|
||||
.leaflet-touching .leaflet-zoom-animated {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
-o-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-container {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging,
|
||||
.leaflet-dragging .leaflet-clickable,
|
||||
.leaflet-dragging .leaflet-container {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
}
|
||||
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline: 0;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-container a.leaflet-active {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #05f;
|
||||
background: white;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
-webkit-border-top-left-radius: 4px;
|
||||
border-top-left-radius: 4px;
|
||||
-webkit-border-top-right-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
-webkit-border-bottom-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
-webkit-border-bottom-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar {
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
-webkit-border-top-left-radius: 7px;
|
||||
border-top-left-radius: 7px;
|
||||
-webkit-border-top-right-radius: 7px;
|
||||
border-top-right-radius: 7px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
-webkit-border-bottom-left-radius: 7px;
|
||||
border-bottom-left-radius: 7px;
|
||||
-webkit-border-bottom-right-radius: 7px;
|
||||
border-bottom-right-radius: 7px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
}
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 22px 'Lucida Console', Monaco, monospace;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in {
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 28px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 7px rgba(0,0,0,0.4);
|
||||
background: #f8f8f9;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 0 5px #bbb;
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
}
|
||||
.leaflet-container .leaflet-control-attribution,
|
||||
.leaflet-container .leaflet-control-scale {
|
||||
font-size: 11px;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
color: black;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
font-size: 11px;
|
||||
text-shadow: 1px 1px 1px #fff;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-control-zoom {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-control-zoom {
|
||||
border: 4px solid rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
-webkit-border-radius: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 19px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
|
||||
background: white;
|
||||
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 4px 4px 0 0;
|
||||
text-align: center;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||
color: #c3c3c3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||
color: #999;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
.leaflet-editing-icon {
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.leaflet-control {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.leaflet-popup-tip {
|
||||
width: 21px;
|
||||
_width: 27px;
|
||||
margin: 0 auto;
|
||||
_margin-top: -3px;
|
||||
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
margin-top: -1px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
.leaflet-control-zoom,
|
||||
.leaflet-control-layers {
|
||||
border: 3px solid #999;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-layers,
|
||||
.leaflet-control-scale-line {
|
||||
background: white;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
.leaflet-control-attribution {
|
||||
border-top: 1px solid #bbb;
|
||||
border-left: 1px solid #bbb;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,75 +0,0 @@
|
||||
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
||||
-webkit-transition: -webkit-transform 0.2s ease-out, opacity 0.2s ease-in;
|
||||
-moz-transition: -moz-transform 0.2s ease-out, opacity 0.2s ease-in;
|
||||
-o-transition: -o-transform 0.2s ease-out, opacity 0.2s ease-in;
|
||||
transition: transform 0.2s ease-out, opacity 0.2s ease-in;
|
||||
}
|
||||
.marker-cluster-small {
|
||||
background-color: rgba(181, 226, 140, 0.6);
|
||||
}
|
||||
.marker-cluster-small div {
|
||||
background-color: rgba(110, 204, 57, 0.6);
|
||||
}
|
||||
|
||||
.marker-cluster-medium {
|
||||
background-color: rgba(241, 211, 87, 0.6);
|
||||
}
|
||||
.marker-cluster-medium div {
|
||||
background-color: rgba(240, 194, 12, 0.6);
|
||||
}
|
||||
|
||||
.marker-cluster-large {
|
||||
background-color: rgba(253, 156, 115, 0.6);
|
||||
}
|
||||
.marker-cluster-large div {
|
||||
background-color: rgba(241, 128, 23, 0.6);
|
||||
}
|
||||
|
||||
.marker-cluster {
|
||||
background-clip: padding-box;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.marker-cluster div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.marker-cluster span {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.leaflet-label {
|
||||
background: #1f1f1f;
|
||||
background-clip: padding-box;
|
||||
border-radius: 4px;
|
||||
border-style: solid;
|
||||
border-width: 0px;
|
||||
display: block;
|
||||
font-weight: 200;
|
||||
font-size: 11pt;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.leaflet-label:before {
|
||||
border-right: 6px solid black;
|
||||
border-right-color: inherit;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: -10px;
|
||||
display: none;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,4 +0,0 @@
|
||||
/** custom additions **/
|
||||
.leaflet-marker-icon {
|
||||
color: #333;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<div ng-controller='bettermap' ng-init="init()">
|
||||
<!-- This solution might work well for other panels that have trouble with heights -->
|
||||
<div style="padding-right:10px;padding-top:10px;height:{{panel.height|| row.height}};overflow:hidden">
|
||||
<div bettermap id='bettermap' params="{{panel}}" style="height:100%"></div>
|
||||
</div>
|
||||
</div>
|
@ -1,268 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/bettermap.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/bettermap/0
|
||||
* == Bettermap
|
||||
* Status: *Experimental*
|
||||
*
|
||||
* Bettermap is called bettermap for lack of a better name. Bettermap uses geographic coordinates to
|
||||
* create clusters of markers on map and shade them orange, yellow and green depending on the
|
||||
* density of the cluster.
|
||||
*
|
||||
* To drill down, click on a cluster. The map will be zoomed and the cluster broken into smaller cluster.
|
||||
* When it no longer makes visual sense to cluster, individual markers will be displayed. Hover over
|
||||
* a marker to see the tooltip value/
|
||||
*
|
||||
* IMPORTANT: bettermap requires an internet connection to download its map panels.
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'./leaflet/leaflet-src',
|
||||
'require',
|
||||
|
||||
'css!./module.css',
|
||||
'css!./leaflet/leaflet.css',
|
||||
'css!./leaflet/plugins.css'
|
||||
],
|
||||
function (angular, app, _, L, localRequire) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.bettermap', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('bettermap', function($scope, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
editorTabs : [
|
||||
{
|
||||
title: 'Queries',
|
||||
src: 'app/partials/querySelect.html'
|
||||
}
|
||||
],
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
status : "Experimental",
|
||||
description : "Displays geo points in clustered groups on a map. The cavaet for this panel is"+
|
||||
" that, for better or worse, it does NOT use the terms facet and it <b>does</b> query "+
|
||||
"sequentially. This however means that it transfers more data and is generally heavier to"+
|
||||
" compute, while showing less actual data. If you have a time filter, it will attempt to"+
|
||||
" show to most recent points in your search, up to your defined limit"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/bettermap/3
|
||||
* === Parameters
|
||||
*
|
||||
* field:: The field that contains the coordinates, in geojson format. GeoJSON is
|
||||
* +[longitude,latitude]+ in an array. This is different from most implementations, which use
|
||||
* latitude, longitude.
|
||||
*/
|
||||
field : null,
|
||||
/** @scratch /panels/bettermap/5
|
||||
* size:: The number of documents to use when drawing the map
|
||||
*/
|
||||
size : 1000,
|
||||
/** @scratch /panels/bettermap/5
|
||||
* spyable:: Should the `inspect` icon be shown?
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/bettermap/5
|
||||
* tooltip:: Which field to use for the tooltip when hovering over a marker
|
||||
*/
|
||||
tooltip : "_id",
|
||||
/** @scratch /panels/bettermap/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
// inorder to use relative paths in require calls, require needs a context to run. Without
|
||||
// setting this property the paths would be relative to the app not this context/file.
|
||||
$scope.requireContext = localRequire;
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.$on('refresh',function(){
|
||||
$scope.get_data();
|
||||
});
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.get_data = function(segment,query_id) {
|
||||
$scope.require(['./leaflet/plugins'], function () {
|
||||
$scope.panel.error = false;
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_.isUndefined($scope.panel.field)) {
|
||||
$scope.panel.error = "Please select a field that contains geo point in [lon,lat] format";
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the field to sort on
|
||||
var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field'));
|
||||
if(timeField.length > 1) {
|
||||
$scope.panel.error = "Time field must be consistent amongst time filters";
|
||||
} else if(timeField.length === 0) {
|
||||
timeField = null;
|
||||
} else {
|
||||
timeField = timeField[0];
|
||||
}
|
||||
|
||||
var _segment = _.isUndefined(segment) ? 0 : segment;
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
var boolQuery = $scope.ejs.BoolQuery();
|
||||
_.each(queries,function(q) {
|
||||
boolQuery = boolQuery.should(querySrv.toEjsObj(q));
|
||||
});
|
||||
|
||||
var request = $scope.ejs.Request().indices(dashboard.indices[_segment])
|
||||
.query($scope.ejs.FilteredQuery(
|
||||
boolQuery,
|
||||
filterSrv.getBoolFilter(filterSrv.ids).must($scope.ejs.ExistsFilter($scope.panel.field))
|
||||
))
|
||||
.fields([$scope.panel.field,$scope.panel.tooltip])
|
||||
.size($scope.panel.size);
|
||||
|
||||
if(!_.isNull(timeField)) {
|
||||
request = request.sort(timeField,'desc');
|
||||
}
|
||||
|
||||
$scope.populate_modal(request);
|
||||
|
||||
var results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
|
||||
if(_segment === 0) {
|
||||
$scope.hits = 0;
|
||||
$scope.data = [];
|
||||
query_id = $scope.query_id = new Date().getTime();
|
||||
}
|
||||
|
||||
// Check for error and abort if found
|
||||
if(!(_.isUndefined(results.error))) {
|
||||
$scope.panel.error = $scope.parse_error(results.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we're still on the same query, if not stop
|
||||
if($scope.query_id === query_id) {
|
||||
|
||||
// Keep only what we need for the set
|
||||
$scope.data = $scope.data.slice(0,$scope.panel.size).concat(_.map(results.hits.hits, function(hit) {
|
||||
return {
|
||||
coordinates : new L.LatLng(hit.fields[$scope.panel.field][1],hit.fields[$scope.panel.field][0]),
|
||||
tooltip : hit.fields[$scope.panel.tooltip]
|
||||
};
|
||||
}));
|
||||
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.$emit('draw');
|
||||
|
||||
// Get $size results then stop querying
|
||||
if($scope.data.length < $scope.panel.size && _segment+1 < dashboard.indices.length) {
|
||||
$scope.get_data(_segment+1,$scope.query_id);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.populate_modal = function(request) {
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('bettermap', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attrs) {
|
||||
|
||||
elem.html('<center><img src="img/load_big.gif"></center>');
|
||||
|
||||
// Receive render events
|
||||
scope.$on('draw',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
scope.$on('render', function(){
|
||||
if(!_.isUndefined(map)) {
|
||||
map.invalidateSize();
|
||||
map.getPanes();
|
||||
}
|
||||
});
|
||||
|
||||
var map, layerGroup;
|
||||
|
||||
function render_panel() {
|
||||
scope.require(['./leaflet/plugins'], function () {
|
||||
scope.panelMeta.loading = false;
|
||||
L.Icon.Default.imagePath = 'app/panels/bettermap/leaflet/images';
|
||||
if(_.isUndefined(map)) {
|
||||
map = L.map(attrs.id, {
|
||||
scrollWheelZoom: false,
|
||||
center: [40, -86],
|
||||
zoom: 10
|
||||
});
|
||||
|
||||
// This could be made configurable?
|
||||
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/22677/256/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
minZoom: 2
|
||||
}).addTo(map);
|
||||
layerGroup = new L.MarkerClusterGroup({maxClusterRadius:30});
|
||||
} else {
|
||||
layerGroup.clearLayers();
|
||||
}
|
||||
|
||||
var markerList = [];
|
||||
|
||||
_.each(scope.data, function(p) {
|
||||
if(!_.isUndefined(p.tooltip) && p.tooltip !== '') {
|
||||
markerList.push(L.marker(p.coordinates).bindLabel(p.tooltip));
|
||||
} else {
|
||||
markerList.push(L.marker(p.coordinates));
|
||||
}
|
||||
});
|
||||
|
||||
layerGroup.addLayers(markerList);
|
||||
|
||||
layerGroup.addTo(map);
|
||||
|
||||
map.fitBounds(_.pluck(scope.data,'coordinates'));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
<div>
|
||||
<h5>Allow saving to</h5>
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<label class="small">Export</label><input type="checkbox" ng-model="panel.save.local" ng-checked="panel.save.local">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<label class="small">Defaults</label><input type="checkbox" ng-model="panel.save.default" ng-checked="panel.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="panel.save.gist" ng-checked="panel.save.gist">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="panel.save.elasticsearch" ng-checked="panel.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="panel.load.local" ng-checked="panel.load.local">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<label class="small">Gist</label><input type="checkbox" ng-model="panel.load.gist" ng-checked="panel.load.gist">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="panel.load.elasticsearch" ng-checked="panel.load.elasticsearch">
|
||||
</div>
|
||||
<div class="span3" ng-show="panel.load.elasticsearch">
|
||||
<label class="small">ES list size</label><input class="input-mini" type="number" ng-model="panel.elasticsearch_size">
|
||||
</div>
|
||||
</div>
|
||||
<h5>Sharing</h5>
|
||||
<div class="row-fluid">
|
||||
<div class="span2" >
|
||||
<label class="small">Allow Sharing</label><input type="checkbox" ng-model="panel.temp" ng-checked="panel.temp">
|
||||
</div>
|
||||
<div class="span2" ng-show="panel.temp">
|
||||
<label class="small">TTL</label><input type="checkbox" ng-model="panel.ttl_enable" ng-checked="panel.temp">
|
||||
</div>
|
||||
<div class="span5" ng-show="panel.temp && panel.ttl_enable">
|
||||
<label class="small">TTL Duration <i class="icon-question-sign" bs-tooltip="'Elasticsearch date math, eg: 1m,1d,1w,30d'"></i></label><input class="input-small" type="text" ng-model="panel.temp_ttl">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,40 +0,0 @@
|
||||
<div>
|
||||
<a class="close" ng-click="dismiss()" href="">×</a>
|
||||
<h4>Load</h4>
|
||||
<div ng-show='panel.load.local'>
|
||||
<h5>Local File</h5>
|
||||
<form>
|
||||
<input type="file" id="dashupload" dash-upload /><br>
|
||||
</form>
|
||||
</div>
|
||||
<div ng-show='panel.load.gist'>
|
||||
<h5>Gist <small>Enter a gist number or url</small></h5>
|
||||
<form>
|
||||
<input type="text" ng-model="gist.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>
|
||||
</div>
|
||||
<div ng-show='panel.load.elasticsearch'>
|
||||
<h5>Elasticsearch</h5>
|
||||
<form class="input-append">
|
||||
<input type="text" ng-model="elasticsearch.query"/>
|
||||
<button ng-click="elasticsearch_dblist(elasticsearch.query)" class='btn'><i class='icon-search'></i></button>
|
||||
</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']">
|
||||
<td><a ng-click="elasticsearch_delete(row._id)"><i class="icon-remove"></i></a></td>
|
||||
<td><a href="#/dashboard/elasticsearch/{{row._id}}">{{row._id}}</a></td>
|
||||
<td><a><i class="icon-share" ng-click="share = dashboard.share_link(row._id,'elasticsearch',row._id)" bs-modal="'app/panels/dashcontrol/share.html'"></i></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -1,6 +0,0 @@
|
||||
<div ng-controller='dashcontrol' ng-init="init()">
|
||||
<label class='small'>Dash Control <tip icon="warning-sign">This panel is deprecated! Please remove it from your dashboard</tip></label>
|
||||
<button class='btn' ng-show="panel.load.gist || panel.load.elasticsearch || panel.load.local" data-placement="bottom" data-unique="1" ng-click="elasticsearch_dblist(elasticsearch.query)" bs-popover="'app/panels/dashcontrol/load.html'"><i class='icon-folder-open'></i> <i class='icon-caret-down'></i></button>
|
||||
<button class='btn' ng-show="panel.save.gist || panel.save.elasticsearch || panel.save.local || panel.save.default" data-placement="bottom" data-unique="1" bs-popover="'app/panels/dashcontrol/save.html'"><i class='icon-save'></i> <i class='icon-caret-down'></i></button>
|
||||
<button ng-show="panel.temp" class='btn' ng-click="elasticsearch_save('temp',panel.temp_ttl)" bs-modal="'app/panels/dashcontrol/share.html'"><i class='icon-share'></i></button>
|
||||
</div>
|
@ -1,198 +0,0 @@
|
||||
/*
|
||||
## Dashcontrol
|
||||
|
||||
### Parameters
|
||||
* save
|
||||
** gist :: Allow saving to gist. Requires registering an oauth domain with Github
|
||||
** elasticsearch :: Allow saving to a special Kibana index within Elasticsearch
|
||||
** local :: Allow saving to local file
|
||||
* load
|
||||
** gist :: Allow loading from gists
|
||||
** elasticsearch :: Allow searching and loading of elasticsearch saved dashboards
|
||||
** local :: Allow loading of dashboards from Elasticsearch
|
||||
* hide_control :: Upon save, hide this panel
|
||||
* elasticsearch_size :: show this many dashboards under the ES section in the load drop down
|
||||
* temp :: Allow saving of temp dashboards
|
||||
* ttl :: Enable setting ttl.
|
||||
* temp_ttl :: How long should temp dashboards persist
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function(angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.dashcontrol', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('dashcontrol', function($scope, $http, timer, dashboard, alertSrv) {
|
||||
$scope.panelMeta = {
|
||||
status : "Deprecated",
|
||||
description : "This panel has been moved to the navigation bar. See the dashboard setting editor to configure it."
|
||||
};
|
||||
|
||||
$scope.panel = $scope.panel || {};
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
save : {
|
||||
gist: false,
|
||||
elasticsearch: true,
|
||||
local: true,
|
||||
'default': true
|
||||
},
|
||||
load : {
|
||||
gist: true,
|
||||
elasticsearch: true,
|
||||
local: true
|
||||
},
|
||||
hide_control: false,
|
||||
elasticsearch_size: 20,
|
||||
temp: true,
|
||||
ttl_enable: true,
|
||||
temp_ttl: '30d'
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
|
||||
$scope.gist = {};
|
||||
$scope.elasticsearch = {};
|
||||
};
|
||||
|
||||
$scope.set_default = function() {
|
||||
if(dashboard.set_default()) {
|
||||
alertSrv.set('Local Default Set',dashboard.current.title+' has been set as your local default','success',5000);
|
||||
} else {
|
||||
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.purge_default = function() {
|
||||
if(dashboard.purge_default()) {
|
||||
alertSrv.set('Local Default Clear','Your local default dashboard has been cleared','success',5000);
|
||||
} else {
|
||||
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.elasticsearch_save = function(type,ttl) {
|
||||
dashboard.elasticsearch_save(
|
||||
type,
|
||||
($scope.elasticsearch.title || dashboard.current.title),
|
||||
($scope.panel.ttl_enable ? ttl : false)
|
||||
).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result._id)) {
|
||||
alertSrv.set('Dashboard Saved','This dashboard has been saved to Elasticsearch as "' +
|
||||
result._id + '"','success',5000);
|
||||
if(type === 'temp') {
|
||||
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
|
||||
}
|
||||
} else {
|
||||
alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.elasticsearch_delete = function(id) {
|
||||
dashboard.elasticsearch_delete(id).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result)) {
|
||||
if(result.found) {
|
||||
alertSrv.set('Dashboard Deleted',id+' has been deleted','success',5000);
|
||||
// Find the deleted dashboard in the cached list and remove it
|
||||
var toDelete = _.where($scope.elasticsearch.dashboards,{_id:id})[0];
|
||||
$scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,toDelete);
|
||||
} else {
|
||||
alertSrv.set('Dashboard Not Found','Could not find '+id+' in Elasticsearch','warning',5000);
|
||||
}
|
||||
} else {
|
||||
alertSrv.set('Dashboard Not Deleted','An error occurred deleting the dashboard','error',5000);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.elasticsearch_dblist = function(query) {
|
||||
dashboard.elasticsearch_list(query,$scope.panel.elasticsearch_size).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result.hits)) {
|
||||
$scope.panel.error = false;
|
||||
$scope.hits = result.hits.total;
|
||||
$scope.elasticsearch.dashboards = result.hits.hits;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.save_gist = function() {
|
||||
dashboard.save_gist($scope.gist.title).then(
|
||||
function(link) {
|
||||
if(!_.isUndefined(link)) {
|
||||
$scope.gist.last = link;
|
||||
alertSrv.set('Gist saved','You will be able to access your exported dashboard file at '+
|
||||
'<a href="'+link+'">'+link+'</a> in a moment','success');
|
||||
} else {
|
||||
alertSrv.set('Save failed','Gist could not be saved','error',5000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.gist_dblist = function(id) {
|
||||
dashboard.gist_list(id).then(
|
||||
function(files) {
|
||||
if(files && files.length > 0) {
|
||||
$scope.gist.files = files;
|
||||
} else {
|
||||
alertSrv.set('Gist Failed','Could not retrieve dashboard list from gist','error',5000);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('dashUpload', function(timer, dashboard, alertSrv){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope) {
|
||||
function file_selected(evt) {
|
||||
var files = evt.target.files; // FileList object
|
||||
|
||||
// unused.. var output = []; // files is a FileList of File objects. List some properties.
|
||||
var readerOnload = function() {
|
||||
return function(e) {
|
||||
dashboard.dash_load(JSON.parse(e.target.result));
|
||||
scope.$apply();
|
||||
};
|
||||
};
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = (readerOnload)(f);
|
||||
reader.readAsText(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the various File API support.
|
||||
if (window.File && window.FileReader && window.FileList && window.Blob) {
|
||||
// Something
|
||||
document.getElementById('dashupload').addEventListener('change', file_selected, false);
|
||||
} else {
|
||||
alertSrv.set('Oops','Sorry, the HTML5 File APIs are not fully supported in this browser.','error');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('gistid', function() {
|
||||
var gist_pattern = /(\d{5,})|([a-z0-9]{10,})|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
|
||||
return function(input) {
|
||||
//return input+"boners"
|
||||
if(!(_.isUndefined(input))) {
|
||||
var output = input.match(gist_pattern);
|
||||
if(!_.isNull(output) && !_.isUndefined(output)) {
|
||||
return output[0].replace(/.*\//, '');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
@ -1,30 +0,0 @@
|
||||
<div>
|
||||
<a class="close" ng-click="dismiss()" href="">×</a>
|
||||
<h4>Save</h4>
|
||||
|
||||
<div ng-show="panel.save.default || panel.save.local">
|
||||
<h5>Locally</h5>
|
||||
<form>
|
||||
<ul class="nav nav-list">
|
||||
<li><a ng-show="panel.save.local" ng-click="dashboard.to_file()"><i class="icon-download"></i> Export to File</a></li>
|
||||
<li><a ng-show="panel.save.default" ng-click="set_default()"><i class="icon-bookmark"></i> Set as My Default</a></li>
|
||||
<li><a ng-show="panel.save.default" ng-click="purge_default()"><i class="icon-ban-circle"></i> Clear My Default</a></li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
<div ng-show="panel.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>
|
||||
</div>
|
||||
<div ng-show="panel.save.elasticsearch">
|
||||
<h5>Elasticsearch</h5>
|
||||
<form class="input-append">
|
||||
<input class='input-medium' placeholder='Title' type="text" ng-model="elasticsearch.title"/>
|
||||
<button class="btn" ng-click="elasticsearch_save('dashboard')"><i class="icon-save"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -1,11 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>{{share.title}} <small>shareable link</small></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label>Share this dashboard with this URL</label>
|
||||
<input ng-model='share.link' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()" ng-change="share = dashboard.share_link(share.title,share.type,share.id)">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>
|
||||
</div>
|
@ -1 +0,0 @@
|
||||
<div></div>
|
@ -1,3 +0,0 @@
|
||||
<div ng-controller='derivequeries' ng-init="init()">
|
||||
<h4>This panel has been removed and replaced with the new topN query type. Click the colored dot associated with a query to configure the, much improved, equivilent of a derived query.</h4>
|
||||
</div>
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
## Derivequeries
|
||||
|
||||
### Parameters
|
||||
* label :: The label to stick over the field
|
||||
* query :: A string to use as a filter for the terms facet
|
||||
* field :: the field to facet on
|
||||
* rest :: include a filter that matches all other terms,
|
||||
* size :: how many queries to generate
|
||||
* fields :: a list of fields known to us
|
||||
* query_mode :: how to create query
|
||||
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.derivequeries', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('derivequeries', function($scope) {
|
||||
$scope.panelMeta = {
|
||||
status : "Deprecated",
|
||||
description : "This panel has been replaced with the 'topN' mode in the query pull down."
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
loading : false,
|
||||
label : "Search",
|
||||
query : "*",
|
||||
ids : [],
|
||||
field : '_type',
|
||||
fields : [],
|
||||
spyable : true,
|
||||
rest : false,
|
||||
size : 5,
|
||||
mode : 'terms only',
|
||||
exclude : [],
|
||||
history : [],
|
||||
remember: 10 // max: 100, angular strap can't take a variable for items param
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.editing = false;
|
||||
};
|
||||
});
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span3"><h6>Popup Position</h6>
|
||||
<select class="input-small" ng-model="panel.micropanel_position" ng-options="f for f in ['top','right','bottom','left']" ng-change="reload_list();"></select></span>
|
||||
</div>
|
||||
<div class="span3"><h6>List Arrangement</h6>
|
||||
<select class="input-small" ng-model="panel.arrange" ng-options="f for f in ['horizontal','vertical']"></select></span>
|
||||
</div>
|
||||
<div class="span3"><h6>Font Size</h6>
|
||||
<select class="input-small" ng-model="panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
|
||||
</div>
|
@ -1,25 +0,0 @@
|
||||
<a class="close" ng-click="dismiss()" href="">×</a>
|
||||
<h4>
|
||||
Micro Analysis of {{micropanel.field}}
|
||||
<i class="pointer icon-search" ng-click="fieldExists(micropanel.field,'must');dismiss();"></i>
|
||||
<i class="pointer icon-ban-circle" ng-click="fieldExists(micropanel.field,'mustNot');dismiss();"></i>
|
||||
<br><small>{{micropanel.count}} events in the table set</small>
|
||||
</h4>
|
||||
<table style="width:480px" class='table table-bordered table-striped table-condensed'>
|
||||
<thead>
|
||||
<th>{{micropanel.field}}</th>
|
||||
<th>Action</th>
|
||||
<th>In set</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat='field in micropanel.values'>
|
||||
<td>{{{true: "__blank__",false:field[0]}[field[0] == ""]}}</td>
|
||||
<td>
|
||||
<i class="pointer icon-search" ng-click="build_search(micropanel.field,field[0],'must');dismiss();"></i>
|
||||
<i class="pointer icon-ban-circle" ng-click="build_search(micropanel.field,field[0],'mustNot');dismiss();"></i>
|
||||
</td>
|
||||
<td>{{field[1]}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span ng-repeat='(field,count) in micropanel.related'><a ng-click="toggle_field(field)">{{field}}</a> ({{Math.round((count / micropanel.count) * 100)}}%),</span>
|
@ -1,3 +0,0 @@
|
||||
<div ng-controller='fields' ng-init="init()">
|
||||
<h4>The 'fields' panel is deprecated.</h4> The table panel now integrates a field selector.
|
||||
</div>
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
## Fields (DEPRECATED)
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.fields', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('fields', function($scope) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
status : "Deprecated",
|
||||
description : "You should not use this table, it does not work anymore. The table panel now"+
|
||||
"integrates a field selector. This module will soon be removed."
|
||||
};
|
||||
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
style : {},
|
||||
arrange : 'vertical',
|
||||
micropanel_position : 'right',
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
// Place holder until I remove this
|
||||
};
|
||||
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
<div>
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
No options here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,15 +0,0 @@
|
||||
<div>
|
||||
<style>
|
||||
.input-query-alias {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
</style>
|
||||
<a class="close" ng-click="render();dismiss();" href="">×</a>
|
||||
<h6>Query Alias</h6>
|
||||
<form>
|
||||
<input class="input-medium input-query-alias" type="text" ng-model="queries.list[id].alias" placeholder='Alias...' />
|
||||
<div>
|
||||
<i ng-repeat="color in queries.colors" class="pointer" ng-class="{'icon-circle-blank':queries.list[id].color == color,'icon-circle':queries.list[id].color != color}" style="color:{{color}}" ng-click="queries.list[id].color = color;render();"> </i>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,90 +0,0 @@
|
||||
<div ng-controller='filtering' ng-init="init()">
|
||||
<style>
|
||||
.filtering-container {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.filter-panel-filter {
|
||||
display:inline-block;
|
||||
vertical-align: top;
|
||||
width: 220px;
|
||||
padding: 5px 5px 0px 5px;
|
||||
border: #555 1px solid;
|
||||
margin: 5px 5px 5px 0px;
|
||||
}
|
||||
.filter-panel-filter ul {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
|
||||
.filter-must {
|
||||
border-top: #7EB26D 3px solid;
|
||||
}
|
||||
.filter-mustNot {
|
||||
border-top: #E24D42 3px solid;
|
||||
}
|
||||
.filter-either {
|
||||
border-top: #EF843C 3px solid;
|
||||
}
|
||||
.filter-deselected {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.filter-action {
|
||||
float:right;
|
||||
margin-bottom: 0px !important;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.filter-mandate {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-apply {
|
||||
float:right;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class='filtering-container'>
|
||||
<span ng-show="filterSrv.ids.length == 0">
|
||||
<h5>No filters available</h5>
|
||||
</span>
|
||||
<div ng-repeat="id in filterSrv.ids" class="small filter-panel-filter filter-{{filterSrv.list[id].mandate}}" ng-class="{'filter-deselected': !filterSrv.list[id].active}">
|
||||
<div>
|
||||
<strong>{{filterSrv.list[id].type}}</strong>
|
||||
<span ng-show="!filterSrv.list[id].editing && isEditable(filterSrv.list[id])" class="filter-mandate" ng-click="filterSrv.list[id].editing = true">
|
||||
{{filterSrv.list[id].mandate}}
|
||||
</span>
|
||||
<span ng-show="!isEditable(filterSrv.list[id])">
|
||||
{{filterSrv.list[id].mandate}}
|
||||
</span>
|
||||
|
||||
<span ng-show="filterSrv.list[id].editing">
|
||||
<select class="input-small" ng-model="filterSrv.list[id].mandate" ng-options="f for f in ['must','mustNot','either']"></select>
|
||||
</span>
|
||||
|
||||
<i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(id)"></i>
|
||||
<i class="filter-action pointer" ng-class="{'icon-check': filterSrv.list[id].active,'icon-check-empty': !filterSrv.list[id].active}" bs-tooltip="'Toggle'" ng-click="toggle(id)"></i>
|
||||
<i class="filter-action pointer icon-edit" ng-hide="filterSrv.list[id].editing || !isEditable(filterSrv.list[id])" bs-tooltip="'Edit'" ng-click="filterSrv.list[id].editing = true"></i>
|
||||
</div>
|
||||
|
||||
<div ng-hide="filterSrv.list[id].editing && isEditable(filterSrv.list[id])">
|
||||
<ul class="unstyled">
|
||||
<li ng-repeat="(key,value) in filterSrv.list[id] track by $index" ng-show="show_key(key)">
|
||||
<strong>{{key}}</strong> : {{value}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form ng-show="filterSrv.list[id].editing && isEditable(filterSrv.list[id])">
|
||||
<ul class="unstyled">
|
||||
<li ng-repeat="key in _.keys(filterSrv.list[id])" ng-show="show_key(key)">
|
||||
<strong>{{key}}</strong> : <input type='text' ng-model="filterSrv.list[id][key]">
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<input type="submit" value="Apply" ng-click="filterSrv.list[id].editing=undefined;refresh()" class="filter-apply btn btn-success btn-mini" bs-tooltip="'Save and refresh'"/>
|
||||
<button ng-click="filterSrv.list[id].editing=undefined" class="filter-apply btn btn-mini" bs-tooltip="'Save without refresh'">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<i class="link icon-plus-sign" ng-click="add()" bs-tooltip="'Add a query filter'" data-placement="right"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
|
||||
## filtering
|
||||
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.filtering', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('filtering', function($scope, filterSrv, $rootScope, dashboard) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
description : "A controllable list of all filters currently applied to the dashboard. You "+
|
||||
"almost certainly want one of these on your dashboard somewhere."
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.$on('filter', function() {
|
||||
$scope.row.notice = true;
|
||||
});
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.filterSrv = filterSrv;
|
||||
};
|
||||
|
||||
$scope.remove = function(id) {
|
||||
filterSrv.remove(id);
|
||||
};
|
||||
|
||||
// This function should be moved to the service
|
||||
$scope.toggle = function(id) {
|
||||
filterSrv.list[id].active = !filterSrv.list[id].active;
|
||||
dashboard.refresh();
|
||||
};
|
||||
|
||||
$scope.add = function(query) {
|
||||
query = query || '*';
|
||||
filterSrv.set({
|
||||
editing : true,
|
||||
type : 'querystring',
|
||||
query : query,
|
||||
mandate : 'must'
|
||||
},undefined,true);
|
||||
};
|
||||
|
||||
$scope.refresh = function() {
|
||||
dashboard.refresh();
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
$rootScope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.show_key = function(key) {
|
||||
return !_.contains(['type','id','alias','mandate','active','editing'],key);
|
||||
};
|
||||
|
||||
$scope.isEditable = function(filter) {
|
||||
var uneditable = ['time'];
|
||||
if(_.contains(uneditable,filter.type)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
});
|
11
src/app/panels/graph/editor.html
Normal file
11
src/app/panels/graph/editor.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<div class="row-fluid">
|
||||
<div class="span4">
|
||||
<label class="small">Graphite Url</label>
|
||||
<input type="text" class="input-large" ng-model="panel.graphiteUrl"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class=small>Targets</label>
|
||||
<textarea ng-model="panel.targets" rows="6" style="width:95%"></textarea>
|
||||
</div>
|
9
src/app/panels/graph/module.html
Normal file
9
src/app/panels/graph/module.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div ng-controller='graph' ng-init="init()">
|
||||
<h2>{{saySomething}}</h2>
|
||||
|
||||
<ul>
|
||||
<li>{{panel.graphiteUrl}}</li>
|
||||
<li>{{panel.targets}}</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
43
src/app/panels/graph/module.js
Normal file
43
src/app/panels/graph/module.js
Normal file
@ -0,0 +1,43 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/text.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/text/0
|
||||
* == text
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The text panel is used for displaying static text formated as markdown, sanitized html or as plain
|
||||
* text.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.graph', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('graph', function($scope) {
|
||||
$scope.panelMeta = {
|
||||
status : "Unstable",
|
||||
description : "A graphite graph module"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.ready = false;
|
||||
$scope.saySomething = "something!";
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Chart value</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>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.mode != 'count'">
|
||||
<label class="small">Value Field <tip>This field must contain a numeric value</tip></label>
|
||||
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-large" ng-model="panel.value_field">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Transform Series</h5>
|
||||
<div class="editor-option" ng-show="panel.mode != 'count'">
|
||||
<label class="small">Scale</label>
|
||||
<input type="text" class="input-mini" ng-model="panel.scale">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Seconds <tip>Normalize intervals to per-second</tip></label><input type="checkbox" ng-model="panel.scaleSeconds" ng-checked="panel.scaleSeconds">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Derivative <tip>Plot the change per interval in the series</tip></label><input type="checkbox" ng-model="panel.derivative" ng-checked="panel.derivative" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h5>Time Options</h5>
|
||||
<div class="editor-row">
|
||||
<div class="editor-option">
|
||||
<label class="small">Time Field</label>
|
||||
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.time_field">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Time correction</label>
|
||||
<select ng-model="panel.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Auto-interval</label><input type="checkbox" ng-model="panel.auto_int" ng-checked="panel.auto_int" />
|
||||
</div>
|
||||
<div class="editor-option" ng-show='panel.auto_int'>
|
||||
<label class="small">Resolution <tip>Shoot for this many data points, rounding to sane intervals</tip></label>
|
||||
<input type="number" class='input-mini' ng-model="panel.resolution" ng-change='set_refresh(true)'/>
|
||||
</div>
|
||||
<div class="editor-option" ng-hide='panel.auto_int'>
|
||||
<label class="small">Interval <tip>Use Elasticsearch date math format (eg 1m, 5m, 1d, 2w, 1y)</tip></label>
|
||||
<input type="text" class='input-mini' ng-model="panel.interval" ng-change='set_refresh(true)'/>
|
||||
</div>
|
||||
</div>
|
@ -1,57 +0,0 @@
|
||||
define([
|
||||
'kbn'
|
||||
],
|
||||
function (kbn) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* manages the interval logic
|
||||
* @param {[type]} interval_string An interval string in the format '1m', '1y', etc
|
||||
*/
|
||||
function Interval(interval_string) {
|
||||
this.string = interval_string;
|
||||
|
||||
var info = kbn.describe_interval(interval_string);
|
||||
this.type = info.type;
|
||||
this.ms = info.sec * 1000 * info.count;
|
||||
|
||||
// does the length of the interval change based on the current time?
|
||||
if (this.type === 'y' || this.type === 'M') {
|
||||
// we will just modify this time object rather that create a new one constantly
|
||||
this.get = this.get_complex;
|
||||
this.date = new Date(0);
|
||||
} else {
|
||||
this.get = this.get_simple;
|
||||
}
|
||||
}
|
||||
|
||||
Interval.prototype = {
|
||||
toString: function () {
|
||||
return this.string;
|
||||
},
|
||||
after: function(current_ms) {
|
||||
return this.get(current_ms, 1);
|
||||
},
|
||||
before: function (current_ms) {
|
||||
return this.get(current_ms, -1);
|
||||
},
|
||||
get_complex: function (current, delta) {
|
||||
this.date.setTime(current);
|
||||
switch(this.type) {
|
||||
case 'M':
|
||||
this.date.setUTCMonth(this.date.getUTCMonth() + delta);
|
||||
break;
|
||||
case 'y':
|
||||
this.date.setUTCFullYear(this.date.getUTCFullYear() + delta);
|
||||
break;
|
||||
}
|
||||
return this.date.getTime();
|
||||
},
|
||||
get_simple: function (current, delta) {
|
||||
return current + (delta * this.ms);
|
||||
}
|
||||
};
|
||||
|
||||
return Interval;
|
||||
|
||||
});
|
@ -1,99 +0,0 @@
|
||||
<div ng-controller='histogram' ng-init="init()" style="min-height:{{panel.height || row.height}}">
|
||||
<style>
|
||||
.histogram-legend {
|
||||
display:inline-block;
|
||||
padding-right:5px
|
||||
}
|
||||
.histogram-legend-dot {
|
||||
display:inline-block;
|
||||
height:10px;
|
||||
width:10px;
|
||||
border-radius:5px;
|
||||
}
|
||||
.histogram-legend-item {
|
||||
display:inline-block;
|
||||
}
|
||||
.histogram-chart {
|
||||
position:relative;
|
||||
}
|
||||
.histogram-options {
|
||||
padding: 5px;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.histogram-options label {
|
||||
margin: 0px 0px 0px 10px !important;
|
||||
}
|
||||
.histogram-options span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* this is actually should be in bootstrap */
|
||||
.form-inline .checkbox {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<span ng-show='panel.options'>
|
||||
<a class="link underline small" ng-show='panel.options' ng-click="options=!options">
|
||||
<i ng-show="!options" class="icon-caret-right"></i><i ng-show="options" class="icon-caret-down"></i> View
|
||||
</a> | 
|
||||
</span>
|
||||
<span ng-show='panel.zoomlinks && data'>
|
||||
<!--<a class='small' ng-click='zoom(0.5)'><i class='icon-zoom-in'></i> Zoom In</a>-->
|
||||
<a class='small' ng-click='zoom(2)'><i class='icon-zoom-out'></i> Zoom Out</a> | 
|
||||
</span>
|
||||
<span ng-show="panel.legend" ng-repeat='series in data' class="histogram-legend">
|
||||
<i class='icon-circle' ng-style="{color: series.info.color}"></i>
|
||||
<span class='small histogram-legend-item'>
|
||||
<span ng-if="panel.show_query">{{series.info.alias || series.info.query}}</span>
|
||||
<span ng-if="!panel.show_query">{{series.info.alias}}</span>
|
||||
<span ng-show="panel.legend_counts"> ({{series.hits}})</span>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-show="panel.legend" class="small"><span ng-show="panel.derivative">change in </span><span class="strong" ng-show="panel.value_field && panel.mode != 'count'">{{panel.value_field}}</span> {{panel.mode}} per <strong ng-hide="panel.scaleSeconds">{{panel.interval}}</strong><strong ng-show="panel.scaleSeconds">1s</strong> | (<strong>{{hits}}</strong> hits)</span>
|
||||
</div>
|
||||
<form class="form-inline bordered histogram-options" ng-show="options">
|
||||
<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.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">
|
||||
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
|
||||
Percent
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
<span>
|
||||
<div class="checkbox">
|
||||
<label class="small">
|
||||
<input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend" ng-change="render()">
|
||||
Legend
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
<span>
|
||||
<label class="small">Interval</label> <select ng-change="set_interval(panel.interval);get_data();" class="input-small" ng-model="panel.interval" ng-options="interval_label(time) for time in _.union([panel.interval],panel.intervals)"></select>
|
||||
</span>
|
||||
</form>
|
||||
<center><img ng-show='panel.loading && _.isUndefined(data)' src="img/load_big.gif"></center>
|
||||
<div histogram-chart class="pointer histogram-chart" params="{{panel}}"></div>
|
||||
</div>
|
@ -1,764 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/histogram.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/histogram/0
|
||||
* == Histogram
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The histogram panel allow for the display of time charts. It includes several modes and tranformations
|
||||
* to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
|
||||
* fields.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'kbn',
|
||||
'moment',
|
||||
'./timeSeries',
|
||||
'jquery.flot',
|
||||
'jquery.flot.events',
|
||||
'jquery.flot.selection',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.byte',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent'
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.histogram', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('histogram', function($scope, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
editorTabs : [
|
||||
{
|
||||
title:'Style',
|
||||
src:'app/panels/histogram/styleEditor.html'
|
||||
},
|
||||
{
|
||||
title:'Queries',
|
||||
src:'app/panels/histogram/queriesEditor.html'
|
||||
},
|
||||
],
|
||||
status : "Stable",
|
||||
description : "A bucketed time series chart of the current query or queries. Uses the "+
|
||||
"Elasticsearch date_histogram facet. If using time stamped indices this panel will query"+
|
||||
" them sequentially to attempt to apply the lighest possible load to your Elasticsearch cluster"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/histogram/3
|
||||
* === Parameters
|
||||
* ==== Axis options
|
||||
* mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
|
||||
* defined. Possible values: count, mean, max, min, total.
|
||||
*/
|
||||
mode : 'count',
|
||||
/** @scratch /panels/histogram/3
|
||||
* time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
|
||||
*/
|
||||
time_field : '@timestamp',
|
||||
/** @scratch /panels/histogram/3
|
||||
* value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
|
||||
*/
|
||||
value_field : null,
|
||||
/** @scratch /panels/histogram/3
|
||||
* x-axis:: Show the x-axis
|
||||
*/
|
||||
'x-axis' : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y-axis:: Show the y-axis
|
||||
*/
|
||||
'y-axis' : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* scale:: Scale the y-axis by this factor
|
||||
*/
|
||||
scale : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y_format:: 'none','bytes','short '
|
||||
*/
|
||||
y_format : 'none',
|
||||
/** @scratch /panels/histogram/5
|
||||
* grid object:: Min and max y-axis values
|
||||
* grid.min::: Minimum y-axis value
|
||||
* grid.max::: Maximum y-axis value
|
||||
*/
|
||||
grid : {
|
||||
max: null,
|
||||
min: 0
|
||||
},
|
||||
/** @scratch /panels/histogram/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Annotations
|
||||
* annotate object:: A query can be specified, the results of which will be displayed as markers on
|
||||
* the chart. For example, for noting code deploys.
|
||||
* annotate.enable::: Should annotations, aka markers, be shown?
|
||||
* annotate.query::: Lucene query_string syntax query to use for markers.
|
||||
* annotate.size::: Max number of markers to show
|
||||
* annotate.field::: Field from documents to show
|
||||
* annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
|
||||
*/
|
||||
annotate : {
|
||||
enable : false,
|
||||
query : "*",
|
||||
size : 20,
|
||||
field : '_type',
|
||||
sort : ['_score','desc']
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Interval options
|
||||
* auto_int:: Automatically scale intervals?
|
||||
*/
|
||||
auto_int : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* resolution:: If auto_int is true, shoot for this many bars.
|
||||
*/
|
||||
resolution : 100,
|
||||
/** @scratch /panels/histogram/3
|
||||
* interval:: If auto_int is set to false, use this as the interval.
|
||||
*/
|
||||
interval : '5m',
|
||||
/** @scratch /panels/histogram/3
|
||||
* interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
|
||||
*/
|
||||
intervals : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Drawing options
|
||||
* lines:: Show line chart
|
||||
*/
|
||||
lines : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* fill:: Area fill factor for line charts, 1-10
|
||||
*/
|
||||
fill : 0,
|
||||
/** @scratch /panels/histogram/3
|
||||
* linewidth:: Weight of lines in pixels
|
||||
*/
|
||||
linewidth : 3,
|
||||
/** @scratch /panels/histogram/3
|
||||
* points:: Show points on chart
|
||||
*/
|
||||
points : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* pointradius:: Size of points in pixels
|
||||
*/
|
||||
pointradius : 5,
|
||||
/** @scratch /panels/histogram/3
|
||||
* bars:: Show bars on chart
|
||||
*/
|
||||
bars : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* stack:: Stack multiple series
|
||||
*/
|
||||
stack : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* spyable:: Show inspect icon
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* zoomlinks:: Show `Zoom Out' link
|
||||
*/
|
||||
zoomlinks : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* options:: Show quick view options section
|
||||
*/
|
||||
options : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend:: Display the legond
|
||||
*/
|
||||
legend : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* show_query:: If no alias is set, should the query be displayed?
|
||||
*/
|
||||
show_query : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* interactive:: Enable click-and-drag to zoom functionality
|
||||
*/
|
||||
interactive : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend_counts:: Show counts in legend
|
||||
*/
|
||||
legend_counts : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Transformations
|
||||
* timezone:: Correct for browser timezone?. Valid values: browser, utc
|
||||
*/
|
||||
timezone : 'browser', // browser or utc
|
||||
/** @scratch /panels/histogram/3
|
||||
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
|
||||
* queries
|
||||
*/
|
||||
percentage : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* zerofill:: Improves the accuracy of line charts at a small performance cost.
|
||||
*/
|
||||
zerofill : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* derivative:: Show each point on the x-axis as the change from the previous point
|
||||
*/
|
||||
derivative : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* tooltip object::
|
||||
* tooltip.value_type::: Individual or cumulative controls how tooltips are display on stacked charts
|
||||
* tooltip.query_as_alias::: If no alias is set, should the query be displayed?
|
||||
*/
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
query_as_alias: true
|
||||
}
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
_.defaults($scope.panel.tooltip,_d.tooltip);
|
||||
_.defaults($scope.panel.annotate,_d.annotate);
|
||||
_.defaults($scope.panel.grid,_d.grid);
|
||||
|
||||
|
||||
|
||||
$scope.init = function() {
|
||||
// Hide view options by default
|
||||
$scope.options = false;
|
||||
$scope.$on('refresh',function(){
|
||||
$scope.get_data();
|
||||
});
|
||||
|
||||
// Always show the query if an alias isn't set. Users can set an alias if the query is too
|
||||
// long
|
||||
$scope.panel.tooltip.query_as_alias = true;
|
||||
|
||||
$scope.get_data();
|
||||
|
||||
};
|
||||
|
||||
$scope.set_interval = function(interval) {
|
||||
if(interval !== 'auto') {
|
||||
$scope.panel.auto_int = false;
|
||||
$scope.panel.interval = interval;
|
||||
} else {
|
||||
$scope.panel.auto_int = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.interval_label = function(interval) {
|
||||
return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
|
||||
};
|
||||
|
||||
/**
|
||||
* The time range effecting the panel
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
$scope.get_time_range = function () {
|
||||
var range = $scope.range = filterSrv.timeRange('last');
|
||||
return range;
|
||||
};
|
||||
|
||||
$scope.get_interval = function () {
|
||||
var interval = $scope.panel.interval,
|
||||
range;
|
||||
if ($scope.panel.auto_int) {
|
||||
range = $scope.get_time_range();
|
||||
if (range) {
|
||||
interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
$scope.panel.interval = interval || '10m';
|
||||
return $scope.panel.interval;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
|
||||
* need to be consulted (like timestamped logstash indicies)
|
||||
*
|
||||
* The results of this function are stored on the scope's data property. This property will be an
|
||||
* array of objects with the properties info, time_series, and hits. These objects are used in the
|
||||
* render_panel function to create the historgram.
|
||||
*
|
||||
* @param {number} segment The segment count, (0 based)
|
||||
* @param {number} query_id The id of the query, generated on the first run and passed back when
|
||||
* this call is made recursively for more segments
|
||||
*/
|
||||
$scope.get_data = function(segment, query_id) {
|
||||
var
|
||||
_range,
|
||||
_interval,
|
||||
request,
|
||||
queries,
|
||||
results;
|
||||
|
||||
if (_.isUndefined(segment)) {
|
||||
segment = 0;
|
||||
}
|
||||
delete $scope.panel.error;
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
_range = $scope.get_time_range();
|
||||
_interval = $scope.get_interval(_range);
|
||||
|
||||
if ($scope.panel.auto_int) {
|
||||
$scope.panel.interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval(_range.from,_range.to,$scope.panel.resolution,0)/1000);
|
||||
}
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
request = $scope.ejs.Request().indices(dashboard.indices[segment]);
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
|
||||
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
// Build the query
|
||||
_.each(queries, function(q) {
|
||||
var query = $scope.ejs.FilteredQuery(
|
||||
querySrv.toEjsObj(q),
|
||||
filterSrv.getBoolFilter(filterSrv.ids)
|
||||
);
|
||||
|
||||
var facet = $scope.ejs.DateHistogramFacet(q.id);
|
||||
|
||||
if($scope.panel.mode === 'count') {
|
||||
facet = facet.field($scope.panel.time_field).global(true);
|
||||
} else {
|
||||
if(_.isNull($scope.panel.value_field)) {
|
||||
$scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
|
||||
return;
|
||||
}
|
||||
facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field).global(true);
|
||||
}
|
||||
facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
|
||||
request = request.facet(facet)
|
||||
.size($scope.panel.annotate.enable ? $scope.panel.annotate.size : 0);
|
||||
});
|
||||
|
||||
if($scope.panel.annotate.enable) {
|
||||
var query = $scope.ejs.FilteredQuery(
|
||||
$scope.ejs.QueryStringQuery($scope.panel.annotate.query || '*'),
|
||||
filterSrv.getBoolFilter(filterSrv.idsByType('time'))
|
||||
);
|
||||
request = request.query(query);
|
||||
|
||||
// This is a hack proposed by @boaz to work around the fact that we can't get
|
||||
// to field data values directly, and we need timestamps as normalized longs
|
||||
request = request.sort([
|
||||
$scope.ejs.Sort($scope.panel.annotate.sort[0]).order($scope.panel.annotate.sort[1]),
|
||||
$scope.ejs.Sort($scope.panel.time_field).desc()
|
||||
]);
|
||||
}
|
||||
|
||||
// Populate the inspector panel
|
||||
$scope.populate_modal(request);
|
||||
|
||||
// Then run it
|
||||
results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
|
||||
$scope.panelMeta.loading = false;
|
||||
if(segment === 0) {
|
||||
$scope.hits = 0;
|
||||
$scope.data = [];
|
||||
$scope.annotations = [];
|
||||
query_id = $scope.query_id = new Date().getTime();
|
||||
}
|
||||
|
||||
// Check for error and abort if found
|
||||
if(!(_.isUndefined(results.error))) {
|
||||
$scope.panel.error = $scope.parse_error(results.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're still on the same query/queries
|
||||
if($scope.query_id === query_id) {
|
||||
|
||||
var i = 0,
|
||||
time_series,
|
||||
hits;
|
||||
|
||||
_.each(queries, function(q) {
|
||||
var query_results = results.facets[q.id];
|
||||
// we need to initialize the data variable on the first run,
|
||||
// and when we are working on the first segment of the data.
|
||||
if(_.isUndefined($scope.data[i]) || segment === 0) {
|
||||
var tsOpts = {
|
||||
interval: _interval,
|
||||
start_date: _range && _range.from,
|
||||
end_date: _range && _range.to,
|
||||
fill_style: $scope.panel.derivative ? 'null' : 'minimal'
|
||||
};
|
||||
time_series = new timeSeries.ZeroFilled(tsOpts);
|
||||
hits = 0;
|
||||
} else {
|
||||
time_series = $scope.data[i].time_series;
|
||||
hits = $scope.data[i].hits;
|
||||
}
|
||||
|
||||
// push each entry into the time series, while incrementing counters
|
||||
_.each(query_results.entries, function(entry) {
|
||||
time_series.addValue(entry.time, entry[$scope.panel.mode]);
|
||||
hits += entry.count; // The series level hits counter
|
||||
$scope.hits += entry.count; // Entire dataset level hits counter
|
||||
});
|
||||
$scope.data[i] = {
|
||||
info: q,
|
||||
time_series: time_series,
|
||||
hits: hits
|
||||
};
|
||||
|
||||
i++;
|
||||
});
|
||||
|
||||
if($scope.panel.annotate.enable) {
|
||||
$scope.annotations = $scope.annotations.concat(_.map(results.hits.hits, function(hit) {
|
||||
var _p = _.omit(hit,'_source','sort','_score');
|
||||
var _h = _.extend(kbn.flatten_json(hit._source),_p);
|
||||
return {
|
||||
min: hit.sort[1],
|
||||
max: hit.sort[1],
|
||||
eventType: "annotation",
|
||||
title: null,
|
||||
description: "<small><i class='icon-tag icon-flip-vertical'></i> "+
|
||||
_h[$scope.panel.annotate.field]+"</small><br>"+
|
||||
moment(hit.sort[1]).format('YYYY-MM-DD HH:mm:ss'),
|
||||
score: hit.sort[0]
|
||||
};
|
||||
}));
|
||||
// Sort the data
|
||||
$scope.annotations = _.sortBy($scope.annotations, function(v){
|
||||
// Sort in reverse
|
||||
return v.score*($scope.panel.annotate.sort[1] === 'desc' ? -1 : 1);
|
||||
});
|
||||
// And slice to the right size
|
||||
$scope.annotations = $scope.annotations.slice(0,$scope.panel.annotate.size);
|
||||
}
|
||||
|
||||
// Tell the histogram directive to render.
|
||||
$scope.$emit('render');
|
||||
|
||||
// If we still have segments left, get them
|
||||
if(segment < dashboard.indices.length-1) {
|
||||
$scope.get_data(segment+1,query_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// function $scope.zoom
|
||||
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
|
||||
$scope.zoom = function(factor) {
|
||||
var _range = filterSrv.timeRange('last');
|
||||
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
|
||||
var _center = _range.to.valueOf() - _timespan/2;
|
||||
|
||||
var _to = (_center + (_timespan*factor)/2);
|
||||
var _from = (_center - (_timespan*factor)/2);
|
||||
|
||||
// If we're not already looking into the future, don't.
|
||||
if(_to > Date.now() && _range.to < Date.now()) {
|
||||
var _offset = _to - Date.now();
|
||||
_from = _from - _offset;
|
||||
_to = Date.now();
|
||||
}
|
||||
|
||||
if(factor > 1) {
|
||||
filterSrv.removeByType('time');
|
||||
}
|
||||
filterSrv.set({
|
||||
type:'time',
|
||||
from:moment.utc(_from).toDate(),
|
||||
to:moment.utc(_to).toDate(),
|
||||
field:$scope.panel.time_field
|
||||
});
|
||||
};
|
||||
|
||||
// I really don't like this function, too much dom manip. Break out into directive?
|
||||
$scope.populate_modal = function(request) {
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
};
|
||||
|
||||
$scope.set_refresh = function (state) {
|
||||
$scope.refresh = state;
|
||||
};
|
||||
|
||||
$scope.close_edit = function() {
|
||||
if($scope.refresh) {
|
||||
$scope.get_data();
|
||||
}
|
||||
$scope.refresh = false;
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('histogramChart', function(dashboard, filterSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div></div>',
|
||||
link: function(scope, elem) {
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
var scale = function(series,factor) {
|
||||
return _.map(series,function(p) {
|
||||
return [p[0],p[1]*factor];
|
||||
});
|
||||
};
|
||||
|
||||
var scaleSeconds = function(series,interval) {
|
||||
return _.map(series,function(p) {
|
||||
return [p[0],p[1]/kbn.interval_to_seconds(interval)];
|
||||
});
|
||||
};
|
||||
|
||||
var derivative = function(series) {
|
||||
return _.map(series, function(p,i) {
|
||||
var _v;
|
||||
if(i === 0 || p[1] === null) {
|
||||
_v = [p[0],null];
|
||||
} else {
|
||||
_v = series[i-1][1] === null ? [p[0],null] : [p[0],p[1]-(series[i-1][1])];
|
||||
}
|
||||
return _v;
|
||||
});
|
||||
};
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// IE doesn't work without this
|
||||
elem.css({height:scope.panel.height || scope.row.height});
|
||||
|
||||
// Populate from the query service
|
||||
try {
|
||||
_.each(scope.data, function(series) {
|
||||
series.label = series.info.alias;
|
||||
series.color = series.info.color;
|
||||
});
|
||||
} catch(e) {return;}
|
||||
|
||||
// Set barwidth based on specified interval
|
||||
var barwidth = kbn.interval_to_ms(scope.panel.interval);
|
||||
|
||||
var stack = scope.panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
try {
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: scope.panel.stack ? scope.panel.percentage : false,
|
||||
stack: scope.panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: scope.panel.lines,
|
||||
// Silly, but fixes bug in stacked percentages
|
||||
fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
|
||||
lineWidth: scope.panel.linewidth,
|
||||
steps: false
|
||||
},
|
||||
bars: {
|
||||
show: scope.panel.bars,
|
||||
fill: 1,
|
||||
barWidth: barwidth/1.5,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: scope.panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: scope.panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxis: {
|
||||
show: scope.panel['y-axis'],
|
||||
min: scope.panel.grid.min,
|
||||
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
|
||||
},
|
||||
xaxis: {
|
||||
timezone: scope.panel.timezone,
|
||||
show: scope.panel['x-axis'],
|
||||
mode: "time",
|
||||
min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
|
||||
max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
|
||||
timeformat: time_format(scope.panel.interval),
|
||||
label: "Datetime",
|
||||
ticks: elem.width()/100
|
||||
},
|
||||
grid: {
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8'
|
||||
}
|
||||
};
|
||||
|
||||
if(scope.panel.y_format === 'bytes') {
|
||||
options.yaxis.mode = "byte";
|
||||
}
|
||||
|
||||
if(scope.panel.y_format === 'short') {
|
||||
options.yaxis.tickFormatter = function(val) {
|
||||
return kbn.shortFormat(val,0);
|
||||
};
|
||||
}
|
||||
|
||||
if(scope.panel.annotate.enable) {
|
||||
options.events = {
|
||||
levels: 1,
|
||||
data: scope.annotations,
|
||||
types: {
|
||||
'annotation': {
|
||||
level: 1,
|
||||
icon: {
|
||||
icon: "icon-tag icon-flip-vertical",
|
||||
size: 20,
|
||||
color: "#222",
|
||||
outline: "#bbb"
|
||||
}
|
||||
}
|
||||
}
|
||||
//xaxis: int // the x axis to attach events to
|
||||
};
|
||||
}
|
||||
|
||||
if(scope.panel.interactive) {
|
||||
options.selection = { mode: "x", color: '#666' };
|
||||
}
|
||||
|
||||
// when rendering stacked bars, we need to ensure each point that has data is zero-filled
|
||||
// so that the stacking happens in the proper order
|
||||
var required_times = [];
|
||||
if (scope.data.length > 1) {
|
||||
required_times = Array.prototype.concat.apply([], _.map(scope.data, function (query) {
|
||||
return query.time_series.getOrderedTimes();
|
||||
}));
|
||||
required_times = _.uniq(required_times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a-b;
|
||||
}), true);
|
||||
}
|
||||
|
||||
|
||||
for (var i = 0; i < scope.data.length; i++) {
|
||||
var _d = scope.data[i].time_series.getFlotPairs(required_times);
|
||||
if(scope.panel.derivative) {
|
||||
_d = derivative(_d);
|
||||
}
|
||||
if(scope.panel.scale !== 1) {
|
||||
_d = scale(_d,scope.panel.scale);
|
||||
}
|
||||
if(scope.panel.scaleSeconds) {
|
||||
_d = scaleSeconds(_d,scope.panel.interval);
|
||||
}
|
||||
scope.data[i].data = _d;
|
||||
}
|
||||
|
||||
scope.plot = $.plot(elem, scope.data, options);
|
||||
|
||||
} catch(e) {
|
||||
// Nothing to do here
|
||||
}
|
||||
}
|
||||
|
||||
function time_format(interval) {
|
||||
var _int = kbn.interval_to_seconds(interval);
|
||||
if(_int >= 2628000) {
|
||||
return "%Y-%m";
|
||||
}
|
||||
if(_int >= 86400) {
|
||||
return "%Y-%m-%d";
|
||||
}
|
||||
if(_int >= 60) {
|
||||
return "%H:%M<br>%m-%d";
|
||||
}
|
||||
|
||||
return "%H:%M:%S";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp;
|
||||
if (item) {
|
||||
if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
(item.series.info.alias || item.series.info.query)+
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
|
||||
item.datapoint[1] - item.datapoint[2] :
|
||||
item.datapoint[1];
|
||||
if(scope.panel.y_format === 'bytes') {
|
||||
value = kbn.byteFormat(value,2);
|
||||
}
|
||||
if(scope.panel.y_format === 'short') {
|
||||
value = kbn.shortFormat(value,2);
|
||||
}
|
||||
timestamp = scope.panel.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
$tooltip
|
||||
.html(
|
||||
group + value + " @ " + timestamp
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
filterSrv.set({
|
||||
type : 'time',
|
||||
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||
field : scope.panel.time_field
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
<h4>Charted</h4>
|
||||
<div ng-include src="'app/partials/querySelect.html'"></div>
|
||||
|
||||
<div class="editor-row">
|
||||
<h4>Markers</h4>
|
||||
|
||||
<div class="small">
|
||||
Here you can specify a query to be plotted on your chart as a marker. Hovering over a marker will display the field you specify below. If more documents are found than the limit you set, they will be scored by Elasticsearch and events that best match your query will be displayed.
|
||||
</div>
|
||||
<style>
|
||||
.querySelect .query {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.querySelect .selected {
|
||||
border: 3px solid;
|
||||
}
|
||||
.querySelect .unselected {
|
||||
border: 0px solid;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
<div class="editor-option">
|
||||
<label class="small">Enable</label>
|
||||
<input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.annotate.enable" ng-checked="panel.annotate.enable">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.annotate.enable">
|
||||
<label class="small">Marker Query</label>
|
||||
<input type="text" ng-change="set_refresh(true)" class="input-large" ng-model="panel.annotate.query"/>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.annotate.enable">
|
||||
<label class="small">Tooltip field</label>
|
||||
<input type="text" class="input-small" ng-model="panel.annotate.field" bs-typeahead="fields.list"/>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.annotate.enable">
|
||||
<label class="small">Limit <tip>Max markers on the chart</tip></label>
|
||||
<input type="number" class="input-mini" ng-model="panel.annotate.size" ng-change="set_refresh(true)"/>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.annotate.enable">
|
||||
<label class="small">Sort <tip>Determine the most relevant markers using this field</tip></label>
|
||||
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.annotate.sort[0]" ng-change="set_refresh(true)" />
|
||||
<i ng-click="panel.annotate.sort[1] = _.toggle(panel.annotate.sort[1],'desc','asc');set_refresh(true)" ng-class="{'icon-chevron-up': panel.annotate.sort[1] == 'asc','icon-chevron-down': panel.annotate.sort[1] == 'desc'}"></i>
|
||||
</div>
|
||||
</div>
|
@ -1,88 +0,0 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Selectable</label><input type="checkbox" ng-model="panel.interactive" ng-checked="panel.interactive">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">xAxis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']"></div>
|
||||
<div class="editor-option">
|
||||
<label class="small">yAxis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']"></div>
|
||||
<div class="editor-option" ng-show="panel.lines">
|
||||
<label class="small">Line Fill</label>
|
||||
<select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"></select>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.lines">
|
||||
<label class="small">Line Width</label>
|
||||
<select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"></select>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.points">
|
||||
<label class="small">Point Radius</label>
|
||||
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_format" ng-options="f for f in ['none','short','bytes']"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Multiple Series</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
|
||||
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Header<h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Zoom</label><input type="checkbox" ng-model="panel.zoomlinks" ng-checked="panel.zoomlinks" />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">View</label><input type="checkbox" ng-model="panel.options" ng-checked="panel.options" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Legend<h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Legend</label><input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend">
|
||||
</div>
|
||||
<div ng-show="panel.legend" class="editor-option">
|
||||
<label class="small">Query <tip>If no alias is set, show the query in the legend</tip></label><input type="checkbox" ng-model="panel.show_query" ng-checked="panel.show_query">
|
||||
</div>
|
||||
<div ng-show="panel.legend" class="editor-option">
|
||||
<label class="small">Counts</label><input type="checkbox" ng-model="panel.legend_counts" ng-checked="panel.legend_counts">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid<h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a href='' ng-click="panel.grid.min = _.toggle(panel.grid.min,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.min"/>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ref='' ng-click="panel.grid.max = _.toggle(panel.grid.max,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.max"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -1,216 +0,0 @@
|
||||
define([
|
||||
'underscore',
|
||||
'./interval'
|
||||
],
|
||||
function (_, Interval) {
|
||||
'use strict';
|
||||
|
||||
var ts = {};
|
||||
|
||||
// map compatable parseInt
|
||||
function base10Int(val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
|
||||
// trim the ms off of a time, but return it with empty ms.
|
||||
function getDatesTime(date) {
|
||||
return Math.floor(date.getTime() / 1000)*1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain graphs require 0 entries to be specified for them to render
|
||||
* properly (like the line graph). So with this we will caluclate all of
|
||||
* the expected time measurements, and fill the missing ones in with 0
|
||||
* @param {object} opts An object specifying some/all of the options
|
||||
*
|
||||
* OPTIONS:
|
||||
* @opt {string} interval The interval notion describing the expected spacing between
|
||||
* each data point.
|
||||
* @opt {date} start_date (optional) The start point for the time series, setting this and the
|
||||
* end_date will ensure that the series streches to resemble the entire
|
||||
* expected result
|
||||
* @opt {date} end_date (optional) The end point for the time series, see start_date
|
||||
* @opt {string} fill_style Either "minimal", or "all" describing the strategy used to zero-fill
|
||||
* the series.
|
||||
*/
|
||||
ts.ZeroFilled = function (opts) {
|
||||
opts = _.defaults(opts, {
|
||||
interval: '10m',
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
fill_style: 'minimal'
|
||||
});
|
||||
|
||||
// the expected differenece between readings.
|
||||
this.interval = new Interval(opts.interval);
|
||||
|
||||
// will keep all values here, keyed by their time
|
||||
this._data = {};
|
||||
this.start_time = opts.start_date && getDatesTime(opts.start_date);
|
||||
this.end_time = opts.end_date && getDatesTime(opts.end_date);
|
||||
this.opts = opts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a row
|
||||
* @param {int} time The time for the value, in
|
||||
* @param {any} value The value at this time
|
||||
*/
|
||||
ts.ZeroFilled.prototype.addValue = function (time, value) {
|
||||
if (time instanceof Date) {
|
||||
time = getDatesTime(time);
|
||||
} else {
|
||||
time = base10Int(time);
|
||||
}
|
||||
if (!isNaN(time)) {
|
||||
this._data[time] = (_.isUndefined(value) ? 0 : value);
|
||||
}
|
||||
this._cached_times = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of the times that have been explicitly set in the series
|
||||
* @param {array} include (optional) list of timestamps to include in the response
|
||||
* @return {array} An array of integer times.
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getOrderedTimes = function (include) {
|
||||
var times = _.map(_.keys(this._data), base10Int);
|
||||
if (_.isArray(include)) {
|
||||
times = times.concat(include);
|
||||
}
|
||||
return _.uniq(times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a - b;
|
||||
}), true);
|
||||
};
|
||||
|
||||
/**
|
||||
* return the rows in the format:
|
||||
* [ [time, value], [time, value], ... ]
|
||||
*
|
||||
* Heavy lifting is done by _get(Min|Default|All)FlotPairs()
|
||||
* @param {array} required_times An array of timestamps that must be in the resulting pairs
|
||||
* @return {array}
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (required_times) {
|
||||
var times = this.getOrderedTimes(required_times),
|
||||
strategy,
|
||||
pairs;
|
||||
|
||||
if(this.opts.fill_style === 'all') {
|
||||
strategy = this._getAllFlotPairs;
|
||||
} else if(this.opts.fill_style === 'null') {
|
||||
strategy = this._getNullFlotPairs;
|
||||
} else {
|
||||
strategy = this._getMinFlotPairs;
|
||||
}
|
||||
|
||||
pairs = _.reduce(
|
||||
times, // what
|
||||
strategy, // how
|
||||
[], // where
|
||||
this // context
|
||||
);
|
||||
|
||||
// if the first or last pair is inside either the start or end time,
|
||||
// add those times to the series with null values so the graph will stretch to contain them.
|
||||
// Removing, flot 0.8.1's max/min params satisfy this
|
||||
/*
|
||||
if (this.start_time && (pairs.length === 0 || pairs[0][0] > this.start_time)) {
|
||||
pairs.unshift([this.start_time, null]);
|
||||
}
|
||||
if (this.end_time && (pairs.length === 0 || pairs[pairs.length - 1][0] < this.end_time)) {
|
||||
pairs.push([this.end_time, null]);
|
||||
}
|
||||
*/
|
||||
|
||||
return pairs;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's on either side of the current time, unless there is already a measurement there or
|
||||
* we are looking at an edge.
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || 0]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's to the right of each time, until the next measurement is reached or we are at the
|
||||
* last measurement
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next;
|
||||
|
||||
result.push([ times[i], this._data[times[i]] || 0 ]);
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
for(; times.length > i && next > expected_next; expected_next = this.interval.after(expected_next)) {
|
||||
result.push([expected_next, 0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Same as min, but fills with nulls
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getNullFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, null]);
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || null]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, null]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
return ts;
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
<div>
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<label class="small">Style</label>
|
||||
<select class="input-small" ng-model="panel.chart" ng-options="f for f in ['bar','pie','list','total']"></select></span>
|
||||
</div>
|
||||
<div class="span2" ng-show="panel.chart == 'total' || panel.chart == 'list'">
|
||||
<label class="small">Font Size</label>
|
||||
<select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
|
||||
</div>
|
||||
<div class="span3" ng-show="panel.chart == 'bar' || panel.chart == 'pie'">
|
||||
<label class="small">Legend</label>
|
||||
<select class="input-small" ng-model="panel.counter_pos" ng-options="f for f in ['above','below','none']"></select></span>
|
||||
</div>
|
||||
<div class="span3" ng-show="panel.chart != 'total' && panel.counter_pos != 'none'">
|
||||
<label class="small" >List Format</label>
|
||||
<select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
|
||||
</div>
|
||||
<div class="span1" ng-show="panel.chart == 'pie'">
|
||||
<label class="small">Donut</label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
|
||||
</div>
|
||||
<div class="span1" ng-show="panel.chart == 'pie'">
|
||||
<label class="small">Tilt</label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
|
||||
</div>
|
||||
<div class="span1" ng-show="panel.chart == 'pie'">
|
||||
<label class="small">Labels</label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,44 +0,0 @@
|
||||
<div ng-controller='hits' ng-init="init()">
|
||||
<div ng-show="panel.counter_pos == 'above' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
|
||||
<!-- vertical legend -->
|
||||
<table class="small" ng-show="panel.arrangement == 'vertical'">
|
||||
<tr ng-repeat="query in data">
|
||||
<td><div style="display:inline-block;border-radius:5px;background:{{query.info.color}};height:10px;width:10px"></div></td> <td style="padding-right:10px;padding-left:10px;">{{query.info.alias}}</td><td>{{query.data[0][1]}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- horizontal legend -->
|
||||
<div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in data" style="float:left;padding-left: 10px;">
|
||||
<span><i class="icon-circle" ng-style="{color:query.info.color}"></i> {{query.info.alias}} ({{query.data[0][1]}}) </span>
|
||||
</div><br>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="clear:both"></div>
|
||||
|
||||
<div ng-show="panel.chart == 'pie' || panel.chart == 'bar'" hits-chart params="{{panel}}" style="position:relative"></div>
|
||||
|
||||
<div ng-show="panel.counter_pos == 'below' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
|
||||
<!-- vertical legend -->
|
||||
<table class="small" ng-show="panel.arrangement == 'vertical'">
|
||||
<tr ng-repeat="query in data">
|
||||
<td><i class="icon-circle" ng-style="{color:query.info.color}"></i></td> <td style="padding-right:10px;padding-left:10px;">{{query.info.alias}}</td><td>{{query.data[0][1]}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- horizontal legend -->
|
||||
<div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in data" style="float:left;padding-left: 10px;">
|
||||
<span><i class="icon-circle" ng-style="{color:query.info.color}"></i></span> {{query.info.alias}} ({{query.data[0][1]}}) </span>
|
||||
</div><br>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-show="panel.chart == 'total'"><div ng-style="panel.style" style="line-height:{{panel.style['font-size']}}">{{hits}}</div></div>
|
||||
|
||||
<span ng-show="panel.chart == 'list'">
|
||||
<div ng-style="panel.style" style="display:inline-block;line-height:{{panel.style['font-size']}}" ng-repeat="query in data">
|
||||
<i class="icon-circle" style="color:{{query.info.color}}"></i> {{query.info.alias}} ({{query.hits}})
|
||||
</div>
|
||||
</span><br ng-show="panel.arrangement == 'vertical' && panel.chart == 'list'">
|
||||
|
||||
</div>
|
@ -1,295 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/hits.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/hits/0
|
||||
* == Hits
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The hits panel displays the number of hits for each of the queries on the dashboard in a
|
||||
* configurable format specified by the `chart' property.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'kbn',
|
||||
|
||||
'jquery.flot',
|
||||
'jquery.flot.pie'
|
||||
], function (angular, app, _, $, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.hits', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('hits', function($scope, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
editorTabs : [
|
||||
{title:'Queries', src:'app/partials/querySelect.html'}
|
||||
],
|
||||
status : "Stable",
|
||||
description : "The total hits for a query or set of queries. Can be a pie chart, bar chart, "+
|
||||
"list, or absolute total of all queries combined"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
style : { "font-size": '10pt'},
|
||||
/** @scratch /panels/hits/3
|
||||
* === Parameters
|
||||
*
|
||||
* arrangement:: The arrangement of the legend. horizontal or vertical
|
||||
*/
|
||||
arrangement : 'horizontal',
|
||||
/** @scratch /panels/hits/3
|
||||
* chart:: bar, pie or none
|
||||
*/
|
||||
chart : 'bar',
|
||||
/** @scratch /panels/hits/3
|
||||
* counter_pos:: The position of the legend, above or below
|
||||
*/
|
||||
counter_pos : 'above',
|
||||
/** @scratch /panels/hits/3
|
||||
* donut:: If the chart is set to pie, setting donut to true will draw a hole in the midle of it
|
||||
*/
|
||||
donut : false,
|
||||
/** @scratch /panels/hits/3
|
||||
* tilt:: If the chart is set to pie, setting tilt to true will tilt it back into an oval
|
||||
*/
|
||||
tilt : false,
|
||||
/** @scratch /panels/hits/3
|
||||
* labels:: If the chart is set to pie, setting labels to true will draw labels in the slices
|
||||
*/
|
||||
labels : true,
|
||||
/** @scratch /panels/hits/3
|
||||
* spyable:: Setting spyable to false disables the inspect icon.
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/hits/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.hits = 0;
|
||||
|
||||
$scope.$on('refresh',function(){
|
||||
$scope.get_data();
|
||||
});
|
||||
$scope.get_data();
|
||||
|
||||
};
|
||||
|
||||
$scope.get_data = function(segment,query_id) {
|
||||
delete $scope.panel.error;
|
||||
$scope.panelMeta.loading = true;
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var _segment = _.isUndefined(segment) ? 0 : segment;
|
||||
var request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
// Build the question part of the query
|
||||
_.each(queries, function(q) {
|
||||
var _q = $scope.ejs.FilteredQuery(
|
||||
querySrv.toEjsObj(q),
|
||||
filterSrv.getBoolFilter(filterSrv.ids));
|
||||
|
||||
request = request
|
||||
.facet($scope.ejs.QueryFacet(q.id)
|
||||
.query(_q)
|
||||
).size(0);
|
||||
});
|
||||
|
||||
// Populate the inspector panel
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
|
||||
// Then run it
|
||||
var results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
if(_segment === 0) {
|
||||
$scope.hits = 0;
|
||||
$scope.data = [];
|
||||
query_id = $scope.query_id = new Date().getTime();
|
||||
}
|
||||
|
||||
// Check for error and abort if found
|
||||
if(!(_.isUndefined(results.error))) {
|
||||
$scope.panel.error = $scope.parse_error(results.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're still on the same query/queries
|
||||
if($scope.query_id === query_id) {
|
||||
var i = 0;
|
||||
_.each(queries, function(q) {
|
||||
var v = results.facets[q.id];
|
||||
var hits = _.isUndefined($scope.data[i]) || _segment === 0 ?
|
||||
v.count : $scope.data[i].hits+v.count;
|
||||
$scope.hits += v.count;
|
||||
|
||||
// Create series
|
||||
$scope.data[i] = {
|
||||
info: q,
|
||||
id: q.id,
|
||||
hits: hits,
|
||||
data: [[i,hits]]
|
||||
};
|
||||
|
||||
i++;
|
||||
});
|
||||
$scope.$emit('render');
|
||||
if(_segment < dashboard.indices.length-1) {
|
||||
$scope.get_data(_segment+1,query_id);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.set_refresh = function (state) {
|
||||
$scope.refresh = state;
|
||||
};
|
||||
|
||||
$scope.close_edit = function() {
|
||||
if($scope.refresh) {
|
||||
$scope.get_data();
|
||||
}
|
||||
$scope.refresh = false;
|
||||
$scope.$emit('render');
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
module.directive('hitsChart', function(querySrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// IE doesn't work without this
|
||||
elem.css({height:scope.panel.height||scope.row.height});
|
||||
|
||||
try {
|
||||
_.each(scope.data,function(series) {
|
||||
series.label = series.info.alias;
|
||||
series.color = series.info.color;
|
||||
});
|
||||
} catch(e) {return;}
|
||||
|
||||
// Populate element
|
||||
try {
|
||||
// Add plot to scope so we can build out own legend
|
||||
if(scope.panel.chart === 'bar') {
|
||||
scope.plot = $.plot(elem, scope.data, {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: { show: false, },
|
||||
bars: { show: true, fill: 1, barWidth: 0.8, horizontal: false },
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxis: { show: true, min: 0, color: "#c8c8c8" },
|
||||
xaxis: { show: false },
|
||||
grid: {
|
||||
borderWidth: 0,
|
||||
borderColor: '#eee',
|
||||
color: "#eee",
|
||||
hoverable: true,
|
||||
},
|
||||
colors: querySrv.colors
|
||||
});
|
||||
}
|
||||
if(scope.panel.chart === 'pie') {
|
||||
scope.plot = $.plot(elem, scope.data, {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
pie: {
|
||||
innerRadius: scope.panel.donut ? 0.4 : 0,
|
||||
tilt: scope.panel.tilt ? 0.45 : 1,
|
||||
radius: 1,
|
||||
show: true,
|
||||
combine: {
|
||||
color: '#999',
|
||||
label: 'The Rest'
|
||||
},
|
||||
stroke: {
|
||||
width: 0
|
||||
},
|
||||
label: {
|
||||
show: scope.panel.labels,
|
||||
radius: 2/3,
|
||||
formatter: function(label, series){
|
||||
return '<div ng-click="build_search(panel.query.field,\''+label+'\')'+
|
||||
' "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
|
||||
label+'<br/>'+Math.round(series.percent)+'%</div>';
|
||||
},
|
||||
threshold: 0.1
|
||||
}
|
||||
}
|
||||
},
|
||||
//grid: { hoverable: true, clickable: true },
|
||||
grid: { hoverable: true, clickable: true },
|
||||
colors: querySrv.colors
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
elem.text(e);
|
||||
}
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
if (item) {
|
||||
var value = scope.panel.chart === 'bar' ?
|
||||
item.datapoint[1] : item.datapoint[1][0][1];
|
||||
$tooltip
|
||||
.html(kbn.query_color_dot(item.series.color, 20) + ' ' + value.toFixed(0))
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.remove();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<form>
|
||||
<h6>Field <tip>2 letter country or state code</tip></h6>
|
||||
<input bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.field" ng-change="set_refresh(true)">
|
||||
</form>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<h6>Max <tip>Maximum countries to plot</tip></h6>
|
||||
<input class="input-mini" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
<div class="span1"><h6>Map</h6>
|
||||
<select ng-change="$emit('render')" class="input-small" ng-model="panel.map" ng-options="f for f in ['world','europe','usa']"></select>
|
||||
</div>
|
||||
</div>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,63 +0,0 @@
|
||||
<div ng-controller='map' ng-init="init()">
|
||||
<style>
|
||||
.jvectormap-label {
|
||||
position: absolute;
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
border: solid 1px #CDCDCD;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
background: #292929;
|
||||
color: white;
|
||||
font-family: sans-serif, Verdana;
|
||||
font-size: smaller;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.jvectormap-zoomin, .jvectormap-zoomout {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
background: #292929;
|
||||
padding: 3px;
|
||||
color: white;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
cursor: pointer;
|
||||
line-height: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jvectormap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.jvectormap-zoomin {
|
||||
display: none;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.jvectormap-zoomout {
|
||||
display: none;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.map-legend {
|
||||
color : #c8c8c8;
|
||||
padding : 10px;
|
||||
font-size: 11pt;
|
||||
font-weight: 200;
|
||||
background-color: #1f1f1f;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 15px;
|
||||
display: none;
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
<div class="jvectormap" map params="{{panel}}" style="height:{{panel.height || row.height}}"></div>
|
||||
</div>
|
@ -1,206 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/map.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/map/0
|
||||
* == Map
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The map panel translates 2 letter country or state codes into shaded regions on a map. Currently
|
||||
* available maps are world, usa and europe.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'config',
|
||||
'./lib/jquery.jvectormap.min'
|
||||
],
|
||||
function (angular, app, _, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.map', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('map', function($scope, $rootScope, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
editorTabs : [
|
||||
{title:'Queries', src:'app/partials/querySelect.html'}
|
||||
],
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
status : "Stable",
|
||||
description : "Displays a map of shaded regions using a field containing a 2 letter country "+
|
||||
", or US state, code. Regions with more hit are shaded darker. Node that this does use the"+
|
||||
" Elasticsearch terms facet, so it is important that you set it to the correct field."
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/map/3
|
||||
* === Parameters
|
||||
*
|
||||
* map:: Map to display. world, usa, europe
|
||||
*/
|
||||
map : "world",
|
||||
/** @scratch /panels/map/3
|
||||
* colors:: An array of colors to use to shade the map. If 2 colors are specified, shades
|
||||
* between them will be used. For example [`#A0E2E2', `#265656']
|
||||
*/
|
||||
colors : ['#A0E2E2', '#265656'],
|
||||
/** @scratch /panels/map/3
|
||||
* size:: Max number of regions to shade
|
||||
*/
|
||||
size : 100,
|
||||
/** @scratch /panels/map/3
|
||||
* exclude:: exclude this array of regions. For example [`US',`BR',`IN']
|
||||
*/
|
||||
exclude : [],
|
||||
/** @scratch /panels/map/3
|
||||
* spyable:: Setting spyable to false disables the inspect icon.
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/map/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
}
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.$on('refresh',function(){$scope.get_data();});
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.get_data = function() {
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
$scope.panelMeta.loading = true;
|
||||
|
||||
|
||||
var request,
|
||||
boolQuery,
|
||||
queries;
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
request = $scope.ejs.Request().indices(dashboard.indices);
|
||||
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
boolQuery = $scope.ejs.BoolQuery();
|
||||
_.each(queries,function(q) {
|
||||
boolQuery = boolQuery.should(querySrv.toEjsObj(q));
|
||||
});
|
||||
|
||||
// Then the insert into facet and make the request
|
||||
request = request
|
||||
.facet($scope.ejs.TermsFacet('map')
|
||||
.field($scope.panel.field)
|
||||
.size($scope.panel.size)
|
||||
.exclude($scope.panel.exclude)
|
||||
.facetFilter($scope.ejs.QueryFilter(
|
||||
$scope.ejs.FilteredQuery(
|
||||
boolQuery,
|
||||
filterSrv.getBoolFilter(filterSrv.ids)
|
||||
)))).size(0);
|
||||
|
||||
$scope.populate_modal(request);
|
||||
|
||||
var results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.hits = results.hits.total;
|
||||
$scope.data = {};
|
||||
_.each(results.facets.map.terms, function(v) {
|
||||
$scope.data[v.term.toUpperCase()] = v.count;
|
||||
});
|
||||
$scope.$emit('render');
|
||||
});
|
||||
};
|
||||
|
||||
// I really don't like this function, too much dom manip. Break out into directive?
|
||||
$scope.populate_modal = function(request) {
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
};
|
||||
|
||||
$scope.build_search = function(field,value) {
|
||||
filterSrv.set({type:'querystring',mandate:'must',query:field+":"+value});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
module.directive('map', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
|
||||
elem.html('<center><img src="img/load_big.gif"></center>');
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Or if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function render_panel() {
|
||||
elem.text('');
|
||||
$('.jvectormap-zoomin,.jvectormap-zoomout,.jvectormap-label').remove();
|
||||
require(['./panels/map/lib/map.'+scope.panel.map], function () {
|
||||
elem.vectorMap({
|
||||
map: scope.panel.map,
|
||||
regionStyle: {initial: {fill: '#8c8c8c'}},
|
||||
zoomOnScroll: false,
|
||||
backgroundColor: null,
|
||||
series: {
|
||||
regions: [{
|
||||
values: scope.data,
|
||||
scale: scope.panel.colors,
|
||||
normalizeFunction: 'polynomial'
|
||||
}]
|
||||
},
|
||||
onRegionLabelShow: function(event, label, code){
|
||||
elem.children('.map-legend').show();
|
||||
var count = _.isUndefined(scope.data[code]) ? 0 : scope.data[code];
|
||||
elem.children('.map-legend').text(label.text() + ": " + count);
|
||||
},
|
||||
onRegionOut: function() {
|
||||
$('.map-legend').hide();
|
||||
},
|
||||
onRegionClick: function(event, code) {
|
||||
var count = _.isUndefined(scope.data[code]) ? 0 : scope.data[code];
|
||||
if (count !== 0) {
|
||||
scope.build_search(scope.panel.field,code);
|
||||
}
|
||||
}
|
||||
});
|
||||
elem.prepend('<span class="map-legend"></span>');
|
||||
$('.map-legend').hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
@ -1,49 +0,0 @@
|
||||
<div class="row-fluid" ng-switch="panel.mode">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<label class="small">Mode</label>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="terms">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<label class="small">Field</label>
|
||||
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.query.field" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<label class="small">Length</label>
|
||||
<input class="input-small" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
<div class="span6">
|
||||
<label class="small">Exclude Terms(s) (comma seperated)</label>
|
||||
<input array-join type="text" ng-model='panel.exclude'></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="goal">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<form style="margin-bottom: 0px">
|
||||
<label class="small">Goal</label>
|
||||
<input type="number" style="width:90%" ng-model="panel.query.goal" ng-change="set_refresh(true)">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="span1">
|
||||
<label class="small"> Donut </label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
|
||||
</div>
|
||||
<div class="span1">
|
||||
<label class="small"> Tilt </label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
|
||||
</div>
|
||||
<div class="span1">
|
||||
<label class="small"> Labels </label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
|
||||
</div>
|
||||
<div class="span3">
|
||||
<label class="small">Legend</label>
|
||||
<select class="input-small" ng-model="panel.legend" ng-options="f for f in ['above','below','none']"></select></span>
|
||||
</div>
|
||||
</div>
|
@ -1,15 +0,0 @@
|
||||
<div ng-controller='pie' ng-init="init()">
|
||||
<style>
|
||||
.pieLabel { pointer-events: none }
|
||||
</style>
|
||||
<div ng-show="panel.legend == 'above'" ng-repeat="query in legend" style="float:left;padding-left: 10px;">
|
||||
<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>
|
||||
</div><br>
|
||||
<div style="clear:both"></div>
|
||||
|
||||
<div pie class="pointer" params="{{panel}}" style="position:relative"></div>
|
||||
|
||||
<div ng-show="panel.legend == 'below'" ng-repeat="query in legend" style="float:left;padding-left: 10px;">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
@ -1,334 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/pie.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/pie/0
|
||||
* == Pie
|
||||
* Status: *Deprecated*
|
||||
*
|
||||
* The pie panel has been largely replaced by the +terms+ panel. It exists for backwards compatibility
|
||||
* for now, but will be removed in a future release
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'kbn',
|
||||
'config'
|
||||
], function (angular, app, _, $, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.pie', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('pie', function($scope, $rootScope, querySrv, dashboard, filterSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
editorTabs : [
|
||||
{title:'Queries', src:'app/partials/querySelect.html'}
|
||||
],
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
status : "Deprecated",
|
||||
description : "Uses an Elasticsearch terms facet to create a pie chart. You should really only"+
|
||||
" point this at not_analyzed fields for that reason. This panel is going away soon, it has"+
|
||||
" <strong>been replaced by the terms panel</strong>. Please use that one instead."
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/pie/3
|
||||
* === Parameters
|
||||
*
|
||||
* mode:: terms or goal. Terms mode finds the top N most popular terms, Goal mode display
|
||||
* progress towards a fix goal in terms of documents matched
|
||||
*/
|
||||
mode : "terms",
|
||||
/** @scratch /panels/pie/3
|
||||
* size:: The max number of results to display in +terms+ mode.
|
||||
*/
|
||||
size : 10,
|
||||
/** @scratch /panels/pie/3
|
||||
* exclude:: Exclude these terms in terms mode
|
||||
*/
|
||||
exclude : [],
|
||||
/** @scratch /panels/pie/3
|
||||
* donut:: Draw a hole in the middle of the pie, creating a tasty donut.
|
||||
*/
|
||||
donut : false,
|
||||
/** @scratch /panels/pie/3
|
||||
* tilt:: Tilt the pie back into an oval shape
|
||||
*/
|
||||
tilt : false,
|
||||
/** @scratch /panels/pie/3
|
||||
* legend:: The location of the legend, above, below or none
|
||||
*/
|
||||
legend : "above",
|
||||
/** @scratch /panels/pie/3
|
||||
* labels:: Set to false to disable drawing labels inside the pie slices
|
||||
*/
|
||||
labels : true,
|
||||
/** @scratch /panels/pie/3
|
||||
* spyable:: Set to false to disable the inspect function.
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/pie/3
|
||||
* ==== Query
|
||||
*
|
||||
* query object:: This confusingly named object has properties to set the terms mode field,
|
||||
* and the fixed goal for the goal mode
|
||||
* query.field::: the field to facet on in terms mode
|
||||
* query.goal::: the fixed goal for goal mode
|
||||
*/
|
||||
query : { field:"_type", goal: 100},
|
||||
/** @scratch /panels/pie/5
|
||||
* ==== Queries
|
||||
*
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
default_field : '_type',
|
||||
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.$on('refresh',function(){$scope.get_data();});
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.set_mode = function(mode) {
|
||||
switch(mode)
|
||||
{
|
||||
case 'terms':
|
||||
$scope.panel.query = {field:"_all"};
|
||||
break;
|
||||
case 'goal':
|
||||
$scope.panel.query = {goal:100};
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.set_refresh = function (state) {
|
||||
$scope.refresh = state;
|
||||
};
|
||||
|
||||
$scope.close_edit = function() {
|
||||
if($scope.refresh) {
|
||||
$scope.get_data();
|
||||
}
|
||||
$scope.refresh = false;
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.get_data = function() {
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
var request = $scope.ejs.Request().indices(dashboard.indices);
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
// This could probably be changed to a BoolFilter
|
||||
var boolQuery = $scope.ejs.BoolQuery();
|
||||
_.each(queries,function(q) {
|
||||
boolQuery = boolQuery.should(querySrv.toEjsObj(q));
|
||||
});
|
||||
|
||||
var results;
|
||||
|
||||
// Terms mode
|
||||
if ($scope.panel.mode === "terms") {
|
||||
request = request
|
||||
.facet($scope.ejs.TermsFacet('pie')
|
||||
.field($scope.panel.query.field || $scope.panel.default_field)
|
||||
.size($scope.panel.size)
|
||||
.exclude($scope.panel.exclude)
|
||||
.facetFilter($scope.ejs.QueryFilter(
|
||||
$scope.ejs.FilteredQuery(
|
||||
boolQuery,
|
||||
filterSrv.getBoolFilter(filterSrv.ids)
|
||||
)))).size(0);
|
||||
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.hits = results.hits.total;
|
||||
$scope.data = [];
|
||||
var k = 0;
|
||||
_.each(results.facets.pie.terms, function(v) {
|
||||
var slice = { label : v.term, data : v.count };
|
||||
$scope.data.push();
|
||||
$scope.data.push(slice);
|
||||
k = k + 1;
|
||||
});
|
||||
$scope.$emit('render');
|
||||
});
|
||||
// Goal mode
|
||||
} else {
|
||||
request = request
|
||||
.query(boolQuery)
|
||||
.filter(filterSrv.getBoolFilter(filterSrv.ids))
|
||||
.size(0);
|
||||
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
results.then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
var complete = results.hits.total;
|
||||
var remaining = $scope.panel.query.goal - complete;
|
||||
$scope.data = [
|
||||
{ label : 'Complete', data : complete, color: '#BF6730' },
|
||||
{ data : remaining, color: '#e2d0c4' }
|
||||
];
|
||||
$scope.$emit('render');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('pie', function(querySrv, filterSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
|
||||
elem.html('<center><img src="img/load_big.gif"></center>');
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Or if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// IE doesn't work without this
|
||||
elem.css({height:scope.panel.height||scope.row.height});
|
||||
|
||||
var label;
|
||||
|
||||
if(scope.panel.mode === 'goal') {
|
||||
label = {
|
||||
show: scope.panel.labels,
|
||||
radius: 0,
|
||||
formatter: function(label, series){
|
||||
var font = parseInt(scope.row.height.replace('px',''),10)/8 + String('px');
|
||||
if(!(_.isUndefined(label))) {
|
||||
return '<div style="font-size:'+font+';font-weight:bold;text-align:center;padding:2px;color:#fff;">'+
|
||||
Math.round(series.percent)+'%</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
};
|
||||
} else {
|
||||
label = {
|
||||
show: scope.panel.labels,
|
||||
radius: 2/3,
|
||||
formatter: function(label, series){
|
||||
return '<div "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
|
||||
label+'<br/>'+Math.round(series.percent)+'%</div>';
|
||||
},
|
||||
threshold: 0.1
|
||||
};
|
||||
}
|
||||
|
||||
var pie = {
|
||||
series: {
|
||||
pie: {
|
||||
innerRadius: scope.panel.donut ? 0.45 : 0,
|
||||
tilt: scope.panel.tilt ? 0.45 : 1,
|
||||
radius: 1,
|
||||
show: true,
|
||||
combine: {
|
||||
color: '#999',
|
||||
label: 'The Rest'
|
||||
},
|
||||
label: label,
|
||||
stroke: {
|
||||
width: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
//grid: { hoverable: true, clickable: true },
|
||||
grid: {
|
||||
backgroundColor: null,
|
||||
hoverable: true,
|
||||
clickable: true
|
||||
},
|
||||
legend: { show: false },
|
||||
colors: querySrv.colors
|
||||
};
|
||||
|
||||
// Populate legend
|
||||
if(elem.is(":visible")){
|
||||
require(['jquery.flot.pie'], function(){
|
||||
scope.legend = $.plot(elem, scope.data, pie).getData();
|
||||
if(!scope.$$phase) {
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
elem.bind('plotclick', function (event, pos, object) {
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
if(scope.panel.mode === 'terms') {
|
||||
filterSrv.set({type:'terms',field:scope.panel.query.field,value:object.series.label});
|
||||
}
|
||||
});
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
elem.bind('plothover', function (event, pos, item) {
|
||||
if (item) {
|
||||
$tooltip
|
||||
.html([
|
||||
kbn.query_color_dot(item.series.color, 15),
|
||||
(item.series.label || ''),
|
||||
parseFloat(item.series.percent).toFixed(1) + '%'
|
||||
].join(' '))
|
||||
.place_tt(pos.pageX, pos.pageY, {
|
||||
offset: 10
|
||||
});
|
||||
} else {
|
||||
$tooltip.remove();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
<div>
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
No options here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
<fieldset>
|
||||
<label class="small">Field</label><br>
|
||||
<input ng-model="querySrv.list[id].field" type="text" bs-typeahead="fields.list" placeholder="Field">
|
||||
<p>
|
||||
<label class="small">Count</label><br>
|
||||
<input ng-model="querySrv.list[id].size" type="number">
|
||||
<p>
|
||||
<label class="small">Union</label><br>
|
||||
<select class="input-small" ng-model="querySrv.list[id].union">
|
||||
<option ng-repeat="mode in ['none','AND','OR']">{{mode}}</option>
|
||||
</select>
|
||||
</fieldset>
|
@ -1,30 +0,0 @@
|
||||
The lucene query type uses <a target="_blank" href='http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax'>LUCENE query string syntax</a> to find matching documents or events within Elasticsearch.
|
||||
|
||||
<h4>Examples</h4>
|
||||
<ul class="unstyled" type="disc">
|
||||
<li class="listitem"><p class="simpara">
|
||||
<code class="literal">status</code> field contains <code class="literal">active</code>
|
||||
</p><pre class="literallayout">status:active</pre></li>
|
||||
<li class="listitem"><p class="simpara">
|
||||
<code class="literal">title</code> field contains <code class="literal">quick</code> or <code class="literal">brown</code>
|
||||
</p><pre class="literallayout">title:(quick brown)</pre></li>
|
||||
<li class="listitem"><p class="simpara">
|
||||
<code class="literal">author</code> field contains the exact phrase <code class="literal">"john smith"</code>
|
||||
</p><pre class="literallayout">author:"John Smith"</pre></li>
|
||||
</ul>
|
||||
|
||||
<p>Wildcard searches can be run on individual terms, using <code class="literal">?</code> to replace
|
||||
a single character, and <code class="literal">*</code> to replace zero or more characters:</p>
|
||||
<pre class="literallayout">qu?ck bro*</pre>
|
||||
|
||||
<ul class="unstyled" type="disc">
|
||||
<li class="listitem"><p class="simpara">
|
||||
Numbers 1..5
|
||||
</p><pre class="literallayout">count:[1 TO 5]</pre></li>
|
||||
<li class="listitem"><p class="simpara">
|
||||
Tags between <code class="literal">alpha</code> and <code class="literal">omega</code>, excluding <code class="literal">alpha</code> and <code class="literal">omega</code>:
|
||||
</p><pre class="literallayout">tag:{alpha TO omega}</pre></li>
|
||||
<li class="listitem"><p class="simpara">
|
||||
Numbers from 10 upwards
|
||||
</p><pre class="literallayout">count:[10 TO *]</pre></li>
|
||||
</ul>
|
@ -1,10 +0,0 @@
|
||||
The regex query allows you to use regular expressions to match terms in the <i>_all</i> field.
|
||||
|
||||
A detailed overview of lucene's regex engine is available here: <a target="_blank" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax">Regular expressions in Elasticsearch</a>
|
||||
|
||||
<h5>A note on anchoring</h5>
|
||||
Lucene’s patterns are always anchored. The pattern provided must match the entire string. For string "abcde":
|
||||
<p>
|
||||
<code>ab.*</code> will match<br>
|
||||
<code>abcd</code> will not match</br>
|
||||
|
@ -1,14 +0,0 @@
|
||||
The topN query uses an <a target="_blank" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html">Elasticsearch terms facet</a> to find the most common terms in a field and build queries from the result. The topN query uses <a target="_blank" href='http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax'>LUCENE query string syntax</a>
|
||||
|
||||
<h4>Parameters</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Field</strong> / The field to facet on. Fields with a large number of unique terms will <a target="_blank" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html#_memory_considerations_2">use more memory</a> to calculate.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Count</strong> / How many queries to generate. The resulting queries will use brightness variations on the original query's color for their own.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Union</strong> / The relation the generated queries have to the original. For example, if your field was set to 'extension', your original query was "user:B.Awesome" and your union was AND. Kibana might generate the following example query: <code>extension:"html" AND (user:B.Awesome)</code>
|
||||
</li>
|
||||
</ul>
|
@ -1,12 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>About the {{help.type}} query</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div ng-include="queryHelpPath(help.type)"></div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger" ng-click="dismiss()">Close</button>
|
||||
</div>
|
@ -1,34 +0,0 @@
|
||||
<div class="panel-query-meta row-fluid" style="width:260px">
|
||||
|
||||
<style>
|
||||
.panel-query-meta fieldset label {
|
||||
margin-top: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset>
|
||||
<select class="input-small" ng-model="querySrv.list[id].type" ng-change="typeChange(querySrv.list[id])">
|
||||
<option ng-repeat="type in queryTypes|esVersion:'require'">{{type.name}}</option>
|
||||
</select>  <a href="" class="small" ng-click="queryHelp(querySrv.list[id].type)"> About the {{querySrv.list[id].type}} query</a>
|
||||
|
||||
<hr class="small">
|
||||
|
||||
<label class="small">Legend value</label>
|
||||
<input type="text" ng-model="querySrv.list[id].alias" placeholder="Alias...">
|
||||
</fieldset>
|
||||
|
||||
<div ng-include src="queryConfig(querySrv.list[id].type)"></div>
|
||||
|
||||
|
||||
<hr class="small">
|
||||
<div>
|
||||
<i ng-repeat="color in querySrv.colors" class="pointer" ng-class="{'icon-circle-blank':querySrv.list[id].color == color,'icon-circle':querySrv.list[id].color != color}" ng-style="{color:color}" ng-click="querySrv.list[id].color = color;render();"> </i>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-mini" ng-click="querySrv.list[id].enable=false;dashboard.refresh();dismiss();" class="pointer">Deactivate</a>
|
||||
<a class="btn btn-mini" ng-class="{active:querySrv.list[id].pin}" ng-click="toggle_pin(id);dismiss();" class="pointer">Pin <i class="icon-pushpin"></i></a>
|
||||
<input class="btn btn-mini" ng-click="dashboard.refresh();dismiss();" type="submit"/ value="Close">
|
||||
</div>
|
||||
</div>
|
@ -1,30 +0,0 @@
|
||||
<div ng-controller='query' ng-init="init()" class="query-panel">
|
||||
<div ng-repeat="id in (unPinnedQueries = (querySrv.ids|pinnedQuery:false))" ng-class="{'short-query': unPinnedQueries.length>1}">
|
||||
<form class="form-search" style="position:relative;margin:5px 0;" ng-submit="refresh()">
|
||||
<span class="begin-query">
|
||||
<i class="pointer" ng-class="queryIcon(querySrv.list[id].type)" ng-show="querySrv.list[id].enable" data-unique="1" bs-popover="'app/panels/query/meta.html'" data-placement="bottomLeft" ng-style="{color: querySrv.list[id].color}"></i>
|
||||
<i class="pointer icon-circle-blank" ng-click="querySrv.list[id].enable=true;dashboard.refresh();" ng-hide="querySrv.list[id].enable" bs-tooltip="'Activate query'" ng-style="{color: querySrv.list[id].color}"></i>
|
||||
<i class="icon-remove-sign pointer remove-query" ng-show="querySrv.ids.length > 1" ng-click="querySrv.remove(id);refresh()"></i>
|
||||
</span>
|
||||
<span>
|
||||
<input class="search-query panel-query" ng-disabled="!querySrv.list[id].enable" ng-class="{ 'input-block-level': unPinnedQueries.length==1, 'last-query': $last, 'has-remove': querySrv.ids.length > 1 }" bs-typeahead="panel.history" data-min-length=0 data-items=100 type="text" ng-model="querySrv.list[id].query" />
|
||||
</span>
|
||||
<span class="end-query">
|
||||
<i class="icon-search pointer" ng-click="refresh()" ng-show="$last"></i>
|
||||
<i class="icon-plus pointer" ng-click="querySrv.set({})" ng-show="$last"></i>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<div style="display:inline-block" ng-repeat="id in querySrv.ids|pinnedQuery:true">
|
||||
<span class="pointer" ng-show="$first" ng-click="panel.pinned = !panel.pinned"><span class="pins">Pinned</span> <i ng-class="{'icon-caret-right':panel.pinned,'icon-caret-left':!panel.pinned}"></i></span>
|
||||
<span ng-show="panel.pinned" class="badge pinned">
|
||||
<i class="icon-circle pointer" ng-show="querySrv.list[id].enable" ng-style="{color: querySrv.list[id].color}" data-unique="1" bs-popover="'app/panels/query/meta.html'" data-placement="bottomLeft"></i>
|
||||
<i class="pointer icon-circle-blank" bs-tooltip="'Activate query'" ng-click="querySrv.list[id].enable=true;dashboard.refresh();" ng-hide="querySrv.list[id].enable" ng-style="{color: querySrv.list[id].color}"></i>
|
||||
<span bs-tooltip="querySrv.list[id].query"> {{querySrv.list[id].alias || querySrv.list[id].query}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span style="display:inline-block" ng-show="unPinnedQueries.length == 0">
|
||||
<i class="icon-search pointer" ng-click="refresh()"></i>
|
||||
<i class="icon-plus pointer" ng-click="querySrv.set({})"></i>
|
||||
</span>
|
||||
</div>
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
|
||||
## query
|
||||
|
||||
### Parameters
|
||||
* query :: A string or an array of querys. String if multi is off, array if it is on
|
||||
This should be fixed, it should always be an array even if its only
|
||||
one element
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
|
||||
'css!./query.css'
|
||||
], function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.query', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('query', function($scope, querySrv, $rootScope, dashboard, $q, $modal) {
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
description : "Manage all of the queries on the dashboard. You almost certainly need one of "+
|
||||
"these somewhere. This panel allows you to add, remove, label, pin and color queries"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
query : "*",
|
||||
pinned : true,
|
||||
history : [],
|
||||
remember: 10 // max: 100, angular strap can't take a variable for items param
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.querySrv = querySrv;
|
||||
|
||||
// A list of query types for the query config popover
|
||||
$scope.queryTypes = _.map(querySrv.queryTypes, function(v,k) {
|
||||
return {
|
||||
name:k,
|
||||
require:v.require
|
||||
};
|
||||
});
|
||||
|
||||
var queryHelpModal = $modal({
|
||||
template: './app/panels/query/helpModal.html',
|
||||
persist: true,
|
||||
show: false,
|
||||
scope: $scope,
|
||||
});
|
||||
|
||||
$scope.init = function() {
|
||||
};
|
||||
|
||||
$scope.refresh = function() {
|
||||
update_history(_.pluck($scope.querySrv.list,'query'));
|
||||
dashboard.refresh();
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
$rootScope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.toggle_pin = function(id) {
|
||||
querySrv.list[id].pin = querySrv.list[id].pin ? false : true;
|
||||
};
|
||||
|
||||
$scope.queryIcon = function(type) {
|
||||
return querySrv.queryTypes[type].icon;
|
||||
};
|
||||
|
||||
$scope.queryConfig = function(type) {
|
||||
return "./app/panels/query/editors/"+(type||'lucene')+".html";
|
||||
};
|
||||
|
||||
$scope.queryHelpPath = function(type) {
|
||||
return "./app/panels/query/help/"+(type||'lucene')+".html";
|
||||
};
|
||||
|
||||
$scope.queryHelp = function(type) {
|
||||
$scope.help = {
|
||||
type: type
|
||||
};
|
||||
$q.when(queryHelpModal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.typeChange = function(q) {
|
||||
var _nq = {
|
||||
id : q.id,
|
||||
type : q.type,
|
||||
query: q.query,
|
||||
alias: q.alias,
|
||||
color: q.color
|
||||
};
|
||||
querySrv.list[_nq.id] = querySrv.defaults(_nq);
|
||||
};
|
||||
|
||||
var update_history = function(query) {
|
||||
if($scope.panel.remember > 0) {
|
||||
$scope.panel.history = _.union(query.reverse(),$scope.panel.history);
|
||||
var _length = $scope.panel.history.length;
|
||||
if(_length > $scope.panel.remember) {
|
||||
$scope.panel.history = $scope.panel.history.slice(0,$scope.panel.remember);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -1,47 +0,0 @@
|
||||
.short-query {
|
||||
display:inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.short-query input.search-query {
|
||||
width: 280px;
|
||||
}
|
||||
.begin-query {
|
||||
position:absolute;
|
||||
left:13px;
|
||||
top:5px;
|
||||
}
|
||||
.end-query {
|
||||
position:absolute;
|
||||
right:15px;
|
||||
top:5px;
|
||||
}
|
||||
.panel-query {
|
||||
padding-left: 35px !important;
|
||||
height: 31px !important;
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
}
|
||||
|
||||
.query-disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.form-search:hover .has-remove {
|
||||
padding-left: 50px !important;
|
||||
}
|
||||
.remove-query {
|
||||
opacity: 0;
|
||||
}
|
||||
.last-query {
|
||||
padding-right: 45px !important;
|
||||
}
|
||||
.form-search:hover .remove-query {
|
||||
opacity: 1;
|
||||
}
|
||||
.query-panel .pins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.query-panel .pinned {
|
||||
margin-right: 5px;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Chart value</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>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Time Field</label>
|
||||
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.time_field">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.mode != 'count'">
|
||||
<label class="small">Value Field <tip>This field must contain a numeric value</tip></label>
|
||||
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-large" ng-model="panel.value_field">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Transform Series</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Derivative <tip>Plot the change per interval in the series</tip></label><input type="checkbox" ng-model="panel.derivative" ng-checked="panel.derivative" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,57 +0,0 @@
|
||||
define([
|
||||
'kbn'
|
||||
],
|
||||
function (kbn) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* manages the interval logic
|
||||
* @param {[type]} interval_string An interval string in the format '1m', '1y', etc
|
||||
*/
|
||||
function Interval(interval_string) {
|
||||
this.string = interval_string;
|
||||
|
||||
var info = kbn.describe_interval(interval_string);
|
||||
this.type = info.type;
|
||||
this.ms = info.sec * 1000 * info.count;
|
||||
|
||||
// does the length of the interval change based on the current time?
|
||||
if (this.type === 'y' || this.type === 'M') {
|
||||
// we will just modify this time object rather that create a new one constantly
|
||||
this.get = this.get_complex;
|
||||
this.date = new Date(0);
|
||||
} else {
|
||||
this.get = this.get_simple;
|
||||
}
|
||||
}
|
||||
|
||||
Interval.prototype = {
|
||||
toString: function () {
|
||||
return this.string;
|
||||
},
|
||||
after: function(current_ms) {
|
||||
return this.get(current_ms, 1);
|
||||
},
|
||||
before: function (current_ms) {
|
||||
return this.get(current_ms, -1);
|
||||
},
|
||||
get_complex: function (current, delta) {
|
||||
this.date.setTime(current);
|
||||
switch(this.type) {
|
||||
case 'M':
|
||||
this.date.setUTCMonth(this.date.getUTCMonth() + delta);
|
||||
break;
|
||||
case 'y':
|
||||
this.date.setUTCFullYear(this.date.getUTCFullYear() + delta);
|
||||
break;
|
||||
}
|
||||
return this.date.getTime();
|
||||
},
|
||||
get_simple: function (current, delta) {
|
||||
return current + (delta * this.ms);
|
||||
}
|
||||
};
|
||||
|
||||
return Interval;
|
||||
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
<div ng-controller='sparklines' ng-init="init()" style="min-height:{{panel.height || row.height}}">
|
||||
<center><img ng-show='panel.loading && _.isUndefined(data)' src="img/load_big.gif"></center>
|
||||
|
||||
|
||||
<div ng-repeat="series in data" style="margin-right:5px;text-align:center;display:inline-block">
|
||||
<small class="strong"><i class="icon-circle" ng-style="{color: series.info.color}"></i> {{series.info.alias}}</small><br>
|
||||
<div style="display:inline-block" sparklines-chart series="series" panel="panel"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -1,386 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/sparklines.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/sparklines/0
|
||||
* == Sparklines
|
||||
* Status: *Experimental*
|
||||
*
|
||||
* The sparklines panel shows tiny time charts. The purpose of these is not to give an exact value,
|
||||
* but rather to show the shape of the time series in a compact manner
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'kbn',
|
||||
'moment',
|
||||
'./timeSeries',
|
||||
|
||||
'jquery.flot',
|
||||
'jquery.flot.time'
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.sparklines', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('sparklines', function($scope, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
editorTabs : [
|
||||
{
|
||||
title:'Queries',
|
||||
src:'app/partials/querySelect.html'
|
||||
}
|
||||
],
|
||||
status : "Experimental",
|
||||
description : "Sparklines are tiny, simple, time series charts, shown seperately. Because "+
|
||||
"sparklines are unclutted by grids, axis markers and colors, they are perfect for spotting"+
|
||||
" change in a series"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/sparklines/3
|
||||
* === Parameters
|
||||
* mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
|
||||
* defined. Possible values: count, mean, max, min, total.
|
||||
*/
|
||||
mode : 'count',
|
||||
/** @scratch /panels/sparklines/3
|
||||
* time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
|
||||
*/
|
||||
time_field : '@timestamp',
|
||||
/** @scratch /panels/sparklines/3
|
||||
* value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
|
||||
*/
|
||||
value_field : null,
|
||||
/** @scratch /panels/sparklines/3
|
||||
* interval:: Sparkline intervals are computed automatically as long as there is a time filter
|
||||
* present. In the absence of a time filter, use this interval.
|
||||
*/
|
||||
interval : '5m',
|
||||
/** @scratch /panels/sparklines/3
|
||||
* spyable:: Show inspect icon
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/sparklines/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
|
||||
$scope.$on('refresh',function(){
|
||||
$scope.get_data();
|
||||
});
|
||||
|
||||
$scope.get_data();
|
||||
|
||||
};
|
||||
|
||||
$scope.interval_label = function(interval) {
|
||||
return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
|
||||
};
|
||||
|
||||
/**
|
||||
* The time range effecting the panel
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
$scope.get_time_range = function () {
|
||||
var range = $scope.range = filterSrv.timeRange('last');
|
||||
return range;
|
||||
};
|
||||
|
||||
$scope.get_interval = function () {
|
||||
var interval = $scope.panel.interval,
|
||||
range;
|
||||
range = $scope.get_time_range();
|
||||
if (range) {
|
||||
interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval(range.from, range.to, 10, 0) / 1000
|
||||
);
|
||||
}
|
||||
$scope.panel.interval = interval || '10m';
|
||||
return $scope.panel.interval;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
|
||||
* need to be consulted (like timestamped logstash indicies)
|
||||
*
|
||||
* The results of this function are stored on the scope's data property. This property will be an
|
||||
* array of objects with the properties info, time_series, and hits. These objects are used in the
|
||||
* render_panel function to create the historgram.
|
||||
*
|
||||
* @param {number} segment The segment count, (0 based)
|
||||
* @param {number} query_id The id of the query, generated on the first run and passed back when
|
||||
* this call is made recursively for more segments
|
||||
*/
|
||||
$scope.get_data = function(segment, query_id) {
|
||||
var
|
||||
_range,
|
||||
_interval,
|
||||
request,
|
||||
queries,
|
||||
results;
|
||||
|
||||
if (_.isUndefined(segment)) {
|
||||
segment = 0;
|
||||
}
|
||||
delete $scope.panel.error;
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
_range = $scope.get_time_range();
|
||||
_interval = $scope.get_interval(_range);
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
request = $scope.ejs.Request().indices(dashboard.indices[segment]);
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
|
||||
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
// Build the query
|
||||
_.each(queries, function(q) {
|
||||
var query = $scope.ejs.FilteredQuery(
|
||||
querySrv.toEjsObj(q),
|
||||
filterSrv.getBoolFilter(filterSrv.ids)
|
||||
);
|
||||
|
||||
var facet = $scope.ejs.DateHistogramFacet(q.id);
|
||||
|
||||
if($scope.panel.mode === 'count') {
|
||||
facet = facet.field($scope.panel.time_field).global(true);
|
||||
} else {
|
||||
if(_.isNull($scope.panel.value_field)) {
|
||||
$scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
|
||||
return;
|
||||
}
|
||||
facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field);
|
||||
}
|
||||
facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
|
||||
request = request.facet(facet)
|
||||
.size(0);
|
||||
});
|
||||
|
||||
// Populate the inspector panel
|
||||
$scope.populate_modal(request);
|
||||
|
||||
// Then run it
|
||||
results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
|
||||
$scope.panelMeta.loading = false;
|
||||
if(segment === 0) {
|
||||
$scope.hits = 0;
|
||||
$scope.data = [];
|
||||
query_id = $scope.query_id = new Date().getTime();
|
||||
}
|
||||
|
||||
// Check for error and abort if found
|
||||
if(!(_.isUndefined(results.error))) {
|
||||
$scope.panel.error = $scope.parse_error(results.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're still on the same query/queries
|
||||
if($scope.query_id === query_id) {
|
||||
|
||||
var i = 0,
|
||||
time_series,
|
||||
hits;
|
||||
|
||||
_.each(queries, function(q) {
|
||||
var query_results = results.facets[q.id];
|
||||
// we need to initialize the data variable on the first run,
|
||||
// and when we are working on the first segment of the data.
|
||||
if(_.isUndefined($scope.data[i]) || segment === 0) {
|
||||
var tsOpts = {
|
||||
interval: _interval,
|
||||
start_date: _range && _range.from,
|
||||
end_date: _range && _range.to,
|
||||
fill_style: 'minimal'
|
||||
};
|
||||
time_series = new timeSeries.ZeroFilled(tsOpts);
|
||||
hits = 0;
|
||||
} else {
|
||||
time_series = $scope.data[i].time_series;
|
||||
hits = $scope.data[i].hits;
|
||||
}
|
||||
|
||||
// push each entry into the time series, while incrementing counters
|
||||
_.each(query_results.entries, function(entry) {
|
||||
time_series.addValue(entry.time, entry[$scope.panel.mode]);
|
||||
hits += entry.count; // The series level hits counter
|
||||
$scope.hits += entry.count; // Entire dataset level hits counter
|
||||
});
|
||||
$scope.data[i] = {
|
||||
info: q,
|
||||
range: $scope.range,
|
||||
time_series: time_series,
|
||||
hits: hits
|
||||
};
|
||||
|
||||
i++;
|
||||
});
|
||||
|
||||
// If we still have segments left, get them
|
||||
if(segment < dashboard.indices.length-1) {
|
||||
$scope.get_data(segment+1,query_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// I really don't like this function, too much dom manip. Break out into directive?
|
||||
$scope.populate_modal = function(request) {
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
};
|
||||
|
||||
$scope.set_refresh = function (state) {
|
||||
$scope.refresh = state;
|
||||
};
|
||||
|
||||
$scope.close_edit = function() {
|
||||
if($scope.refresh) {
|
||||
$scope.get_data();
|
||||
}
|
||||
$scope.refresh = false;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('sparklinesChart', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
series: '=',
|
||||
panel: '='
|
||||
},
|
||||
template: '<div></div>',
|
||||
link: function(scope, elem) {
|
||||
|
||||
// Receive render events
|
||||
scope.$watch('series',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
var derivative = function(series) {
|
||||
return _.map(series, function(p,i) {
|
||||
var _v;
|
||||
if(i === 0 || p[1] === null) {
|
||||
_v = [p[0],null];
|
||||
} else {
|
||||
_v = series[i-1][1] === null ? [p[0],null] : [p[0],p[1]-(series[i-1][1])];
|
||||
}
|
||||
return _v;
|
||||
});
|
||||
};
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
// IE doesn't work without this
|
||||
elem.css({height:"30px",width:"100px"});
|
||||
|
||||
// Populate element
|
||||
//try {
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
// Silly, but fixes bug in stacked percentages
|
||||
fill: 0,
|
||||
lineWidth: 2,
|
||||
steps: false
|
||||
},
|
||||
points: { radius:2 },
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxis: {
|
||||
show: false
|
||||
},
|
||||
xaxis: {
|
||||
show: false,
|
||||
mode: "time",
|
||||
min: _.isUndefined(scope.series.range.from) ? null : scope.series.range.from.getTime(),
|
||||
max: _.isUndefined(scope.series.range.to) ? null : scope.series.range.to.getTime()
|
||||
},
|
||||
grid: {
|
||||
hoverable: false,
|
||||
show: false
|
||||
}
|
||||
};
|
||||
// when rendering stacked bars, we need to ensure each point that has data is zero-filled
|
||||
// so that the stacking happens in the proper order
|
||||
var required_times = [];
|
||||
required_times = scope.series.time_series.getOrderedTimes();
|
||||
required_times = _.uniq(required_times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a-b;
|
||||
}), true);
|
||||
|
||||
var _d = {
|
||||
data : scope.panel.derivative ?
|
||||
derivative(scope.series.time_series.getFlotPairs(required_times)) :
|
||||
scope.series.time_series.getFlotPairs(required_times),
|
||||
label : scope.series.info.alias,
|
||||
color : elem.css('color'),
|
||||
};
|
||||
|
||||
$.plot(elem, [_d], options);
|
||||
|
||||
//} catch(e) {
|
||||
// console.log(e);
|
||||
//}
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
if (item) {
|
||||
$tooltip
|
||||
.html(
|
||||
item.datapoint[1] + " @ " + moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss')
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
@ -1,216 +0,0 @@
|
||||
define([
|
||||
'underscore',
|
||||
'./interval'
|
||||
],
|
||||
function (_, Interval) {
|
||||
'use strict';
|
||||
|
||||
var ts = {};
|
||||
|
||||
// map compatable parseInt
|
||||
function base10Int(val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
|
||||
// trim the ms off of a time, but return it with empty ms.
|
||||
function getDatesTime(date) {
|
||||
return Math.floor(date.getTime() / 1000)*1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain graphs require 0 entries to be specified for them to render
|
||||
* properly (like the line graph). So with this we will caluclate all of
|
||||
* the expected time measurements, and fill the missing ones in with 0
|
||||
* @param {object} opts An object specifying some/all of the options
|
||||
*
|
||||
* OPTIONS:
|
||||
* @opt {string} interval The interval notion describing the expected spacing between
|
||||
* each data point.
|
||||
* @opt {date} start_date (optional) The start point for the time series, setting this and the
|
||||
* end_date will ensure that the series streches to resemble the entire
|
||||
* expected result
|
||||
* @opt {date} end_date (optional) The end point for the time series, see start_date
|
||||
* @opt {string} fill_style Either "minimal", or "all" describing the strategy used to zero-fill
|
||||
* the series.
|
||||
*/
|
||||
ts.ZeroFilled = function (opts) {
|
||||
opts = _.defaults(opts, {
|
||||
interval: '10m',
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
fill_style: 'minimal'
|
||||
});
|
||||
|
||||
// the expected differenece between readings.
|
||||
this.interval = new Interval(opts.interval);
|
||||
|
||||
// will keep all values here, keyed by their time
|
||||
this._data = {};
|
||||
this.start_time = opts.start_date && getDatesTime(opts.start_date);
|
||||
this.end_time = opts.end_date && getDatesTime(opts.end_date);
|
||||
this.opts = opts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a row
|
||||
* @param {int} time The time for the value, in
|
||||
* @param {any} value The value at this time
|
||||
*/
|
||||
ts.ZeroFilled.prototype.addValue = function (time, value) {
|
||||
if (time instanceof Date) {
|
||||
time = getDatesTime(time);
|
||||
} else {
|
||||
time = base10Int(time);
|
||||
}
|
||||
if (!isNaN(time)) {
|
||||
this._data[time] = (_.isUndefined(value) ? 0 : value);
|
||||
}
|
||||
this._cached_times = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of the times that have been explicitly set in the series
|
||||
* @param {array} include (optional) list of timestamps to include in the response
|
||||
* @return {array} An array of integer times.
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getOrderedTimes = function (include) {
|
||||
var times = _.map(_.keys(this._data), base10Int);
|
||||
if (_.isArray(include)) {
|
||||
times = times.concat(include);
|
||||
}
|
||||
return _.uniq(times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a - b;
|
||||
}), true);
|
||||
};
|
||||
|
||||
/**
|
||||
* return the rows in the format:
|
||||
* [ [time, value], [time, value], ... ]
|
||||
*
|
||||
* Heavy lifting is done by _get(Min|Default|All)FlotPairs()
|
||||
* @param {array} required_times An array of timestamps that must be in the resulting pairs
|
||||
* @return {array}
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (required_times) {
|
||||
var times = this.getOrderedTimes(required_times),
|
||||
strategy,
|
||||
pairs;
|
||||
|
||||
if(this.opts.fill_style === 'all') {
|
||||
strategy = this._getAllFlotPairs;
|
||||
} else if(this.opts.fill_style === 'null') {
|
||||
strategy = this._getNullFlotPairs;
|
||||
} else {
|
||||
strategy = this._getMinFlotPairs;
|
||||
}
|
||||
|
||||
pairs = _.reduce(
|
||||
times, // what
|
||||
strategy, // how
|
||||
[], // where
|
||||
this // context
|
||||
);
|
||||
|
||||
// if the first or last pair is inside either the start or end time,
|
||||
// add those times to the series with null values so the graph will stretch to contain them.
|
||||
// Removing, flot 0.8.1's max/min params satisfy this
|
||||
/*
|
||||
if (this.start_time && (pairs.length === 0 || pairs[0][0] > this.start_time)) {
|
||||
pairs.unshift([this.start_time, null]);
|
||||
}
|
||||
if (this.end_time && (pairs.length === 0 || pairs[pairs.length - 1][0] < this.end_time)) {
|
||||
pairs.push([this.end_time, null]);
|
||||
}
|
||||
*/
|
||||
|
||||
return pairs;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's on either side of the current time, unless there is already a measurement there or
|
||||
* we are looking at an edge.
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || 0]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's to the right of each time, until the next measurement is reached or we are at the
|
||||
* last measurement
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next;
|
||||
|
||||
result.push([ times[i], this._data[times[i]] || 0 ]);
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
for(; times.length > i && next > expected_next; expected_next = this.interval.after(expected_next)) {
|
||||
result.push([expected_next, 0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Same as min, but fills with nulls
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getNullFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, null]);
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || null]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, null]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
return ts;
|
||||
});
|
@ -1,49 +0,0 @@
|
||||
<div class="row-fluid">
|
||||
<div class="section span6">
|
||||
<h5>Columns</h5>
|
||||
<form class="input-append editor-option">
|
||||
<input bs-typeahead="fields.list" type="text" class="input-small" ng-model='newfield'>
|
||||
<button class="btn" ng-click="toggle_field(newfield);newfield=''"><i class="icon-plus"></i></button>
|
||||
</form><br>
|
||||
<span style="margin-left:3px" ng-repeat="field in $parent.panel.fields" class="label">{{field}} <i class="pointer icon-remove-sign" ng-click="toggle_field(field)"></i></span>
|
||||
</div>
|
||||
<div class="section span6">
|
||||
<h5>Hightlighted Fields</h5>
|
||||
<form class="input-append editor-option">
|
||||
<input bs-typeahead="fields.list" type="text" class="input-small" ng-model='newhighlight' ng-change="set_refresh(true)">
|
||||
<button class="btn" ng-click="toggle_highlight(newhighlight);newhighlight=''"><i class="icon-plus"></i></button>
|
||||
</form><br>
|
||||
<span style="margin-left:3px" ng-repeat="field in $parent.panel.highlight" class="label">{{field}} <i class="pointer icon-remove-sign" ng-click="toggle_highlight(field);set_refresh(true)" ></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Options</h5>
|
||||
<div class="editor-option">
|
||||
<h6>Header</h6><input type="checkbox" ng-model="panel.header" ng-checked="panel.header">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<h6>Sorting</h6><input type="checkbox" ng-model="panel.sortable" ng-checked="panel.sortable">
|
||||
</div>
|
||||
<div class="editor-option" style="white-space:nowrap" ng-show='panel.sortable'>
|
||||
<h6>Sort</h6>
|
||||
<input class="input-small" bs-typeahead="fields.list" ng-model="panel.sort[0]" type="text"></input>
|
||||
<i ng-click="set_sort(panel.sort[0])" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i>
|
||||
</div>
|
||||
<div class="editor-option"><h6>Font Size</h6>
|
||||
<select class="input-small" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<h6>Trim Factor <tip>Trim fields to this long divided by # of rows. Requires data refresh.</tip></h6>
|
||||
<input type="number" class="input-small" ng-model="panel.trimFactor" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<h6>Local Time <tip>Adjust time field to browser's local time</tip></h6><input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.localTime" ng-checked="panel.localTime">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.localTime">
|
||||
<h6>Time Field</h6>
|
||||
<input type="text" class="input-small" ng-model="panel.timeField" ng-change="set_refresh(true)" bs-typeahead="fields.list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,62 +0,0 @@
|
||||
<a class="close" ng-click="dismiss()" href="">×</a>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<span>
|
||||
<i class="pointer icon-search" ng-click="fieldExists(micropanel.field,'must');dismiss();" bs-tooltip="'Find events with this field'"></i>
|
||||
<i class="pointer icon-ban-circle" ng-click="fieldExists(micropanel.field,'mustNot');dismiss();" bs-tooltip="'Find events without this field'"></i>
|
||||
<strong>Micro Analysis of {{micropanel.field}}</strong>
|
||||
<span ng-show="micropanel.hasArrays">
|
||||
as
|
||||
<a class="link" ng-class="{'strong':micropanel.grouped}" ng-click="toggle_micropanel(micropanel.field,true)">Groups</a> /
|
||||
<a class="link" ng-class="{'strong':!micropanel.grouped}" ng-click="toggle_micropanel(micropanel.field,false)">Singles</a>
|
||||
</span>
|
||||
</span>
|
||||
<table style="width:100%;table-layout:fixed" class='table table-striped table-unpadded'>
|
||||
<thead>
|
||||
<th style="width:260px">{{micropanel.field}}</th>
|
||||
<th style="width:40px">Action</th>
|
||||
<th style="width:100px;text-align:right">Count / {{micropanel.count}} events</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat='field in micropanel.values'>
|
||||
<td style="word-wrap:break-word">{{{true: "__blank__", false:field[0] }[field[0] == '' || field[0] == undefined]|tableTruncate:panel.trimFactor:3}}</td>
|
||||
<td>
|
||||
<i class="pointer icon-search" ng-click="build_search(micropanel.field,field[0]);dismiss();"></i>
|
||||
<i class="pointer icon-ban-circle" ng-click="build_search(micropanel.field,field[0],true);dismiss();"></i>
|
||||
</td>
|
||||
<td class="progress" style="position:relative">
|
||||
<style scoped>
|
||||
.progress {
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
<div bs-tooltip="percent(field[1],data.length)" class="bar" ng-class="micropanelColor($index)" ng-style="{width: percent(field[1],data.length)}"></div>
|
||||
<span style="position:absolute;right:20px;">{{field[1]}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="progress nomargin" ng-show="micropanel.grouped">
|
||||
<div ng-repeat='field in micropanel.values' bs-tooltip="field[0]+' ('+percent(field[1],data.length)+')'" class="bar {{micropanelColor($index)}}" ng-style="{width: percent(field[1],data.length)};"></div>
|
||||
</div>
|
||||
<div>
|
||||
<span ng-repeat="field in micropanel.related|orderBy:'count':true|limitTo:micropanel.limit track by $index"><a ng-click="toggle_field(field.name)" bs-tooltip="'Toggle {{field.name}} column'">{{field.name}}</a> ({{Math.round((field.count / micropanel.count) * 100)}}%), </span>
|
||||
<a class="link" ng-show="micropanel.related.length > micropanel.limit" ng-click="micropanel.limit = micropanel.limit + 10">More <i class="icon-caret-right"></i></a>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="btn-group">
|
||||
<a class="btn dropdown-toggle pointer" data-toggle="dropdown">
|
||||
<i class="icon-list-ol"></i> Terms
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="termsModal(field,'bar');dismiss();">Bar</a></li>
|
||||
<li><a ng-click="termsModal(field,'pie');dismiss();">Pie</a></li>
|
||||
<li><a ng-click="termsModal(field,'table');dismiss();">Table</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@ -1,45 +0,0 @@
|
||||
<div class="modal-body">
|
||||
<style>
|
||||
.timepicker-to-column {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.timepicker-input input {
|
||||
outline: 0 !important;
|
||||
border: 0px !important;
|
||||
-webkit-box-shadow: 0;
|
||||
-moz-box-shadow: 0;
|
||||
box-shadow: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timepicker-input input::-webkit-outer-spin-button,
|
||||
.timepicker-input input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input.timepicker-date {
|
||||
width: 90px;
|
||||
}
|
||||
input.timepicker-hms {
|
||||
width: 20px;
|
||||
}
|
||||
input.timepicker-ms {
|
||||
width: 25px;
|
||||
}
|
||||
div.timepicker-now {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
<h4>Top 10 terms in field {{modalField}}</h4>
|
||||
<div>
|
||||
<kibana-simple-panel ng-click="dismiss();" type="'{{facetType}}'" panel='{{facetPanel}}' ng-cloak></kibana-simple-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<form name="input" style="margin-bottom:0">
|
||||
<button ng-click="dismiss();" class="btn btn-danger">Close</button>
|
||||
</form>
|
||||
</div>
|
@ -1,124 +0,0 @@
|
||||
<div ng-controller='table' ng-init='init()'>
|
||||
<style>
|
||||
.table-doc-table {
|
||||
margin-left: 0px !important;
|
||||
overflow-y: auto;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div bindonce ng-class="{'span3':panel.field_list}" ng-if="panel.field_list">
|
||||
<div class="sidebar-nav">
|
||||
<strong>Fields <i class=" icon-chevron-sign-left pointer " ng-click="panel.field_list = !panel.field_list" bs-tooltip="'Hide field list'"></i></strong><p>
|
||||
<div class="small">
|
||||
<span class="link small" ng-click="panel.all_fields = true;" ng-if="fields.list.length" ng-class="{strong:panel.all_fields}">
|
||||
All ({{fields.list.length}})</span> /
|
||||
<span class="link small" ng-click="panel.all_fields = false;" ng-class="{strong:!panel.all_fields}">
|
||||
Current ({{current_fields.length || 0}})</span>
|
||||
|
||||
</div>
|
||||
<div><input type="text" class="input-medium" placeholder="Type to filter..." ng-model="fieldFilter"></div>
|
||||
|
||||
<ul class="unstyled" style="{{panel.overflow}}:{{panel.height || row.height}};overflow-y:auto;overflow-x:hidden;" ng-if="panel.all_fields">
|
||||
<li ng-style="panel.style" ng-repeat="field in fields.list|filter:fieldFilter|orderBy:identity">
|
||||
<i class="pointer" ng-class="{'icon-check': columns[field],'icon-check-empty': _.isUndefined(columns[field])}" ng-click="toggle_field(field)"></i>
|
||||
<a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: columns[field]}" bo-text="field"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="unstyled" style="{{panel.overflow}}:{{panel.height || row.height}};overflow-y:auto;overflow-x:hidden;" ng-if="!panel.all_fields">
|
||||
<li ng-style="panel.style" ng-repeat="field in current_fields|filter:fieldFilter|orderBy:identity">
|
||||
<i class="pointer" ng-class="{'icon-check': columns[field],'icon-check-empty': _.isUndefined(columns[field])}" ng-click="toggle_field(field)"></i>
|
||||
<a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: columns[field]}" bo-text="field"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div style="{{panel.overflow}}:{{panel.height || row.height}};" ng-class="{'span9':panel.field_list,'span12':!panel.field_list}" class="table-doc-table">
|
||||
<i class="pull-left icon-chevron-sign-right pointer" ng-click="panel.field_list = !panel.field_list" bs-tooltip="'Show field list'" ng-show="!panel.field_list"></i>
|
||||
<div class="row-fluid" ng-show="panel.paging">
|
||||
<div class="span1 offset1" style="text-align:right">
|
||||
<i ng-click="panel.offset = 0" ng-show="panel.offset > 0" class='icon-circle-arrow-left pointer'></i>
|
||||
<i ng-click="panel.offset = (panel.offset - panel.size)" ng-show="panel.offset > 0" class='icon-arrow-left pointer'></i>
|
||||
</div>
|
||||
<div class="span8" style="text-align:center">
|
||||
<strong>{{panel.offset}}</strong> to <strong>{{panel.offset + data.slice(panel.offset,panel.offset+panel.size).length}}</strong>
|
||||
<small> of {{data.length}} available for paging</small>
|
||||
</div>
|
||||
<div class="span1" style="text-align:left">
|
||||
<i ng-click="panel.offset = (panel.offset + panel.size)" ng-show="data.length > panel.offset+panel.size" class='icon-arrow-right pointer'></i>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table-hover table table-condensed" ng-style="panel.style">
|
||||
<thead ng-show="panel.header">
|
||||
<th ng-show="panel.fields.length<1">_source (select columns from the list to the left)</th>
|
||||
<th style="white-space:nowrap" ng-repeat="field in panel.fields">
|
||||
<i ng-show="!$first" class="pointer link icon-caret-left" ng-click="_.move(panel.fields,$index,$index-1)"></i>
|
||||
|
||||
<span class="pointer" ng-click="set_sort(field)" ng-show='panel.sortable'>
|
||||
{{field}}
|
||||
<i ng-show='field == panel.sort[0]' class="pointer link" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i>
|
||||
</span>
|
||||
<span ng-show='!panel.sortable'>{{field}}</span>
|
||||
<i ng-show="!$last" class="pointer link icon-caret-right" ng-click="_.move(panel.fields,$index,$index+1)"></i>
|
||||
</th>
|
||||
|
||||
</thead>
|
||||
<tbody bindonce 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-if="panel.fields.length<1" bo-text="event._source|stringify|tableTruncate:panel.trimFactor:1"></td>
|
||||
<td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields">
|
||||
<span ng-if="!panel.localTime || panel.timeField != field" bo-html="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"></span>
|
||||
<span ng-if="panel.localTime && panel.timeField == field" bo-html="event.sort[1]|tableLocalTime:event"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="event.kibana.details">
|
||||
<td colspan={{panel.fields.length}} ng-switch="event.kibana.view">
|
||||
<span>
|
||||
View:
|
||||
<a class="link" ng-class="{'strong':event.kibana.view == 'table'}" ng-click="event.kibana.view = 'table'">Table</a> /
|
||||
<a class="link" ng-class="{'strong':event.kibana.view == 'json'}" ng-click="event.kibana.view = 'json'">JSON</a> /
|
||||
<a class="link" ng-class="{'strong':event.kibana.view == 'raw'}" ng-click="event.kibana.view = 'raw'">Raw</a>
|
||||
<i class="link pull-right icon-chevron-up" ng-click="toggle_details(event)"></i>
|
||||
</span>
|
||||
<table class='table table-bordered table-condensed' ng-switch-when="table">
|
||||
<thead>
|
||||
<th>Field</th>
|
||||
<th>Action</th>
|
||||
<th>Value</th>
|
||||
</thead>
|
||||
<tr ng-repeat="(key,value) in event.kibana._source track by $index" ng-class-odd="'odd'">
|
||||
<td bo-text="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>
|
||||
<i class='icon-ban-circle pointer' ng-click="build_search(key,value,true)" bs-tooltip="'Add filter to NOT match this value'"></i>
|
||||
<i class="pointer icon-th" ng-click="toggle_field(key)" bs-tooltip="'Toggle table column'"></i>
|
||||
</td>
|
||||
<!-- At some point we need to create a more efficient way of applying the filter pipeline -->
|
||||
<td style="white-space:pre-wrap" bo-html="value|noXml|urlLink|stringify"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<pre style="white-space:pre-wrap" bo-html="without_kibana(event)|tableJson:2" ng-switch-when="json"></pre>
|
||||
<pre bo-html="without_kibana(event)|tableJson:1" ng-switch-when="raw"></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row-fluid" ng-show="panel.paging">
|
||||
<div class="span1 offset3" style="text-align:right">
|
||||
<i ng-click="panel.offset = 0" ng-show="panel.offset > 0" class='icon-circle-arrow-left pointer'></i>
|
||||
<i ng-click="panel.offset = (panel.offset - panel.size)" ng-show="panel.offset > 0" class='icon-arrow-left pointer'></i>
|
||||
</div>
|
||||
<div class="span4" style="text-align:center">
|
||||
<strong>{{panel.offset}}</strong> to <strong>{{panel.offset + data.slice(panel.offset,panel.offset+panel.size).length}}</strong>
|
||||
<small> of {{data.length}} available for paging</small>
|
||||
</div>
|
||||
<div class="span1" style="text-align:left">
|
||||
<i ng-click="panel.offset = (panel.offset + panel.size)" ng-show="data.length > panel.offset+panel.size" class='icon-arrow-right pointer'></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,493 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/table.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/table/0
|
||||
* == table
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The table panel contains a sortable, pagable view of documents that. It can be arranged into
|
||||
* defined columns and offers several interactions, such as performing adhoc terms aggregations.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'kbn',
|
||||
'moment',
|
||||
],
|
||||
function (angular, app, _, kbn, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.table', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('table', function($rootScope, $scope, $modal, $q, $compile, fields, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
editorTabs : [
|
||||
{
|
||||
title:'Paging',
|
||||
src: 'app/panels/table/pagination.html'
|
||||
},
|
||||
{
|
||||
title:'Queries',
|
||||
src: 'app/partials/querySelect.html'
|
||||
}
|
||||
],
|
||||
status: "Stable",
|
||||
description: "A paginated table of records matching your query or queries. Click on a row to "+
|
||||
"expand it and review all of the fields associated with that document. <p>"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/table/5
|
||||
* === Parameters
|
||||
*
|
||||
* size:: The number of hits to show per page
|
||||
*/
|
||||
size : 100, // Per page
|
||||
/** @scratch /panels/table/5
|
||||
* pages:: The number of pages available
|
||||
*/
|
||||
pages : 5, // Pages available
|
||||
/** @scratch /panels/table/5
|
||||
* offset:: The current page
|
||||
*/
|
||||
offset : 0,
|
||||
/** @scratch /panels/table/5
|
||||
* sort:: An array describing the sort order of the table. For example [`@timestamp',`desc']
|
||||
*/
|
||||
sort : ['_score','desc'],
|
||||
/** @scratch /panels/table/5
|
||||
* overflow:: The css overflow property. `min-height' (expand) or `auto' (scroll)
|
||||
*/
|
||||
overflow: 'min-height',
|
||||
/** @scratch /panels/table/5
|
||||
* fields:: the fields used a columns of the table, in an array.
|
||||
*/
|
||||
fields : [],
|
||||
/** @scratch /panels/table/5
|
||||
* highlight:: The fields on which to highlight, in an array
|
||||
*/
|
||||
highlight : [],
|
||||
/** @scratch /panels/table/5
|
||||
* sortable:: Set sortable to false to disable sorting
|
||||
*/
|
||||
sortable: true,
|
||||
/** @scratch /panels/table/5
|
||||
* header:: Set to false to hide the table column names
|
||||
*/
|
||||
header : true,
|
||||
/** @scratch /panels/table/5
|
||||
* paging:: Set to false to hide the paging controls of the table
|
||||
*/
|
||||
paging : true,
|
||||
/** @scratch /panels/table/5
|
||||
* field_list:: Set to false to hide the list of fields. The user will be able to expand it,
|
||||
* but it will be hidden by default
|
||||
*/
|
||||
field_list: true,
|
||||
/** @scratch /panels/table/5
|
||||
* all_fields:: Set to true to show all fields in the mapping, not just the current fields in
|
||||
* the table.
|
||||
*/
|
||||
all_fields: false,
|
||||
/** @scratch /panels/table/5
|
||||
* trimFactor:: The trim factor is the length at which to truncate fields takinging into
|
||||
* consideration the number of columns in the table. For example, a trimFactor of 100, with 5
|
||||
* columns in the table, would trim each column at 20 character. The entirety of the field is
|
||||
* still available in the expanded view of the event.
|
||||
*/
|
||||
trimFactor: 300,
|
||||
/** @scratch /panels/table/5
|
||||
* localTime:: Set to true to adjust the timeField to the browser's local time
|
||||
*/
|
||||
localTime: false,
|
||||
/** @scratch /panels/table/5
|
||||
* timeField:: If localTime is set to true, this field will be adjusted to the browsers local time
|
||||
*/
|
||||
timeField: '@timestamp',
|
||||
/** @scratch /panels/table/5
|
||||
* spyable:: Set to false to disable the inspect icon
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/table/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
style : {'font-size': '9pt'},
|
||||
normTimes : true,
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.columns = {};
|
||||
_.each($scope.panel.fields,function(field) {
|
||||
$scope.columns[field] = true;
|
||||
});
|
||||
|
||||
$scope.Math = Math;
|
||||
$scope.identity = angular.identity;
|
||||
$scope.$on('refresh',function(){$scope.get_data();});
|
||||
|
||||
$scope.fields = fields;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
// Create a percent function for the view
|
||||
$scope.percent = kbn.to_percent;
|
||||
|
||||
$scope.termsModal = function(field,chart) {
|
||||
$scope.modalField = field;
|
||||
showModal(
|
||||
'{"height":"300px","chart":"'+chart+'","field":"'+field+'"}','terms');
|
||||
};
|
||||
|
||||
$scope.statsModal = function(field) {
|
||||
$scope.modalField = field;
|
||||
showModal(
|
||||
'{"field":"'+field+'"}','statistics');
|
||||
};
|
||||
|
||||
var showModal = function(panel,type) {
|
||||
$scope.facetPanel = panel;
|
||||
$scope.facetType = type;
|
||||
|
||||
// create a new modal. Can't reuse one modal unforunately as the directive will not
|
||||
// re-render on show.
|
||||
var panelModal = $modal({
|
||||
template: './app/panels/table/modal.html',
|
||||
persist: true,
|
||||
show: false,
|
||||
scope: $scope,
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
// and show it
|
||||
$q.when(panelModal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggle_micropanel = function(field,groups) {
|
||||
var docs = _.map($scope.data,function(_d){return _d.kibana._source;});
|
||||
var topFieldValues = kbn.top_field_values(docs,field,10,groups);
|
||||
$scope.micropanel = {
|
||||
field: field,
|
||||
grouped: groups,
|
||||
values : topFieldValues.counts,
|
||||
hasArrays : topFieldValues.hasArrays,
|
||||
related : kbn.get_related_fields(docs,field),
|
||||
limit: 10,
|
||||
count: _.countBy(docs,function(doc){return _.contains(_.keys(doc),field);})['true']
|
||||
};
|
||||
};
|
||||
|
||||
$scope.micropanelColor = function(index) {
|
||||
var _c = ['bar-success','bar-warning','bar-danger','bar-info','bar-primary'];
|
||||
return index > _c.length ? '' : _c[index];
|
||||
};
|
||||
|
||||
$scope.set_sort = function(field) {
|
||||
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();
|
||||
};
|
||||
|
||||
$scope.toggle_field = function(field) {
|
||||
if (_.indexOf($scope.panel.fields,field) > -1) {
|
||||
$scope.panel.fields = _.without($scope.panel.fields,field);
|
||||
delete $scope.columns[field];
|
||||
} else {
|
||||
$scope.panel.fields.push(field);
|
||||
$scope.columns[field] = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggle_highlight = function(field) {
|
||||
if (_.indexOf($scope.panel.highlight,field) > -1) {
|
||||
$scope.panel.highlight = _.without($scope.panel.highlight,field);
|
||||
} else {
|
||||
$scope.panel.highlight.push(field);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggle_details = function(row) {
|
||||
row.kibana.details = row.kibana.details ? false : true;
|
||||
row.kibana.view = row.kibana.view || 'table';
|
||||
//row.kibana.details = !row.kibana.details ? $scope.without_kibana(row) : false;
|
||||
};
|
||||
|
||||
$scope.page = function(page) {
|
||||
$scope.panel.offset = page*$scope.panel.size;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.build_search = function(field,value,negate) {
|
||||
var query;
|
||||
// This needs to be abstracted somewhere
|
||||
if(_.isArray(value)) {
|
||||
query = "(" + _.map(value,function(v){return angular.toJson(v);}).join(" AND ") + ")";
|
||||
} else if (_.isUndefined(value)) {
|
||||
query = '*';
|
||||
negate = !negate;
|
||||
} else {
|
||||
query = angular.toJson(value);
|
||||
}
|
||||
$scope.panel.offset = 0;
|
||||
filterSrv.set({type:'field',field:field,query:query,mandate:(negate ? 'mustNot':'must')});
|
||||
};
|
||||
|
||||
$scope.fieldExists = function(field,mandate) {
|
||||
filterSrv.set({type:'exists',field:field,mandate:mandate});
|
||||
};
|
||||
|
||||
$scope.get_data = function(segment,query_id) {
|
||||
var
|
||||
_segment,
|
||||
request,
|
||||
boolQuery,
|
||||
queries,
|
||||
sort;
|
||||
|
||||
$scope.panel.error = false;
|
||||
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sort = [$scope.ejs.Sort($scope.panel.sort[0]).order($scope.panel.sort[1])];
|
||||
if($scope.panel.localTime) {
|
||||
sort.push($scope.ejs.Sort($scope.panel.timeField).order($scope.panel.sort[1]));
|
||||
}
|
||||
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
|
||||
_segment = _.isUndefined(segment) ? 0 : segment;
|
||||
$scope.segment = _segment;
|
||||
|
||||
request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
|
||||
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
boolQuery = $scope.ejs.BoolQuery();
|
||||
_.each(queries,function(q) {
|
||||
boolQuery = boolQuery.should(querySrv.toEjsObj(q));
|
||||
});
|
||||
|
||||
request = request.query(
|
||||
$scope.ejs.FilteredQuery(
|
||||
boolQuery,
|
||||
filterSrv.getBoolFilter(filterSrv.ids)
|
||||
))
|
||||
.highlight(
|
||||
$scope.ejs.Highlight($scope.panel.highlight)
|
||||
.fragmentSize(2147483647) // Max size of a 32bit unsigned int
|
||||
.preTags('@start-highlight@')
|
||||
.postTags('@end-highlight@')
|
||||
)
|
||||
.size($scope.panel.size*$scope.panel.pages)
|
||||
.sort(sort);
|
||||
|
||||
$scope.populate_modal(request);
|
||||
|
||||
// Populate scope when we have results
|
||||
request.doSearch().then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
|
||||
if(_segment === 0) {
|
||||
$scope.hits = 0;
|
||||
$scope.data = [];
|
||||
$scope.current_fields = [];
|
||||
query_id = $scope.query_id = new Date().getTime();
|
||||
}
|
||||
|
||||
// Check for error and abort if found
|
||||
if(!(_.isUndefined(results.error))) {
|
||||
$scope.panel.error = $scope.parse_error(results.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we're still on the same query, if not stop
|
||||
if($scope.query_id === query_id) {
|
||||
|
||||
// This is exceptionally expensive, especially on events with a large number of fields
|
||||
$scope.data = $scope.data.concat(_.map(results.hits.hits, function(hit) {
|
||||
var
|
||||
_h = _.clone(hit),
|
||||
_p = _.omit(hit,'_source','sort','_score');
|
||||
|
||||
// _source is kind of a lie here, never display it, only select values from it
|
||||
_h.kibana = {
|
||||
_source : _.extend(kbn.flatten_json(hit._source),_p),
|
||||
highlight : kbn.flatten_json(hit.highlight||{})
|
||||
};
|
||||
|
||||
// Kind of cheating with the _.map here, but this is faster than kbn.get_all_fields
|
||||
$scope.current_fields = $scope.current_fields.concat(_.keys(_h.kibana._source));
|
||||
|
||||
return _h;
|
||||
}));
|
||||
|
||||
$scope.current_fields = _.uniq($scope.current_fields);
|
||||
$scope.hits += results.hits.total;
|
||||
|
||||
// Sort the data
|
||||
$scope.data = _.sortBy($scope.data, function(v){
|
||||
if(!_.isUndefined(v.sort)) {
|
||||
return v.sort[0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Reverse if needed
|
||||
if($scope.panel.sort[1] === 'desc') {
|
||||
$scope.data.reverse();
|
||||
}
|
||||
|
||||
// Keep only what we need for the set
|
||||
$scope.data = $scope.data.slice(0,$scope.panel.size * $scope.panel.pages);
|
||||
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not sorting in reverse chrono order, query every index for
|
||||
// size*pages results
|
||||
// Otherwise, only get size*pages results then stop querying
|
||||
if (($scope.data.length < $scope.panel.size*$scope.panel.pages ||
|
||||
!((_.contains(filterSrv.timeField(),$scope.panel.sort[0])) && $scope.panel.sort[1] === 'desc')) &&
|
||||
_segment+1 < dashboard.indices.length) {
|
||||
$scope.get_data(_segment+1,$scope.query_id);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
$scope.populate_modal = function(request) {
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
};
|
||||
|
||||
$scope.without_kibana = function (row) {
|
||||
var _c = _.clone(row);
|
||||
delete _c.kibana;
|
||||
return _c;
|
||||
};
|
||||
|
||||
$scope.set_refresh = function (state) {
|
||||
$scope.refresh = state;
|
||||
};
|
||||
|
||||
$scope.close_edit = function() {
|
||||
if($scope.refresh) {
|
||||
$scope.get_data();
|
||||
}
|
||||
$scope.refresh = false;
|
||||
};
|
||||
|
||||
$scope.locate = function(obj, path) {
|
||||
path = path.split('.');
|
||||
var arrayPattern = /(.+)\[(\d+)\]/;
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
var match = arrayPattern.exec(path[i]);
|
||||
if (match) {
|
||||
obj = obj[match[1]][parseInt(match[2],10)];
|
||||
} else {
|
||||
obj = obj[path[i]];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
// This also escapes some xml sequences
|
||||
module.filter('tableHighlight', function() {
|
||||
return function(text) {
|
||||
if (!_.isUndefined(text) && !_.isNull(text) && text.toString().length > 0) {
|
||||
return text.toString().
|
||||
replace(/&/g, '&').
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>').
|
||||
replace(/\r?\n/g, '<br/>').
|
||||
replace(/@start-highlight@/g, '<code class="highlight">').
|
||||
replace(/@end-highlight@/g, '</code>');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('tableTruncate', function() {
|
||||
return function(text,length,factor) {
|
||||
if (!_.isUndefined(text) && !_.isNull(text) && text.toString().length > 0) {
|
||||
return text.length > length/factor ? text.substr(0,length/factor)+'...' : text;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.filter('tableJson', function() {
|
||||
var json;
|
||||
return function(text,prettyLevel) {
|
||||
if (!_.isUndefined(text) && !_.isNull(text) && text.toString().length > 0) {
|
||||
json = angular.toJson(text,prettyLevel > 0 ? true : false);
|
||||
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
if(prettyLevel > 1) {
|
||||
/* jshint maxlen: false */
|
||||
json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
||||
var cls = 'number';
|
||||
if (/^"/.test(match)) {
|
||||
if (/:$/.test(match)) {
|
||||
cls = 'key strong';
|
||||
} else {
|
||||
cls = '';
|
||||
}
|
||||
} else if (/true|false/.test(match)) {
|
||||
cls = 'boolean';
|
||||
} else if (/null/.test(match)) {
|
||||
cls = 'null';
|
||||
}
|
||||
return '<span class="' + cls + '">' + match + '</span>';
|
||||
});
|
||||
}
|
||||
return json;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
// WIP
|
||||
module.filter('tableLocalTime', function(){
|
||||
return function(text,event) {
|
||||
return moment(event.sort[1]).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
|
||||
};
|
||||
});
|
||||
|
||||
});
|
@ -1,27 +0,0 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<h6>Show Controls</h6><input type="checkbox" ng-model="panel.paging" ng-checked="panel.paging">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<h6>Overflow</h6>
|
||||
<select class="input-small" ng-model="panel.overflow" ng-options="f.value as f.key for f in [{key:'scroll',value:'height'},{key:'expand',value:'min-height'}]"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<h6>Per Page</h6>
|
||||
<input type="number" class="input-mini" ng-model="panel.size" ng-change="get_data()">
|
||||
</div>
|
||||
<div class="span1">
|
||||
<h6> </h6>
|
||||
<center><i class='icon-remove'></i><center>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<h6>Page limit</h6>
|
||||
<input type="number" class="input-mini" ng-model="panel.pages" ng-change="get_data()">
|
||||
</div>
|
||||
<div class="span2 large">
|
||||
<h6>Pageable</h6>
|
||||
<strong class="large">= {{panel.size * panel.pages}}</strong>
|
||||
</div>
|
||||
</div>
|
@ -1,51 +0,0 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<label class="small">Field</label>
|
||||
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.field" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<label class="small">Length</label>
|
||||
<input class="input-small" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
|
||||
</div>
|
||||
<div class="span3">
|
||||
<label class="small">Order</label>
|
||||
<select class="input-medium" ng-model="panel.order" ng-options="f for f in ['count','term','reverse_count','reverse_term']" ng-change="set_refresh(true)"></select></span>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<label class="small">Exclude Terms(s) (comma seperated)</label>
|
||||
<input array-join type="text" ng-model='panel.exclude'></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<label class="small">Style</label>
|
||||
<select class="input-small" ng-model="panel.chart" ng-options="f for f in ['bar','pie','table']"></select></span>
|
||||
</div>
|
||||
<div class="span2" ng-show="panel.chart == 'table'">
|
||||
<label class="small">Font Size</label>
|
||||
<select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
|
||||
</div>
|
||||
<div class="span2" ng-show="panel.chart == 'bar' || panel.chart == 'pie'">
|
||||
<label class="small">Legend</label>
|
||||
<select class="input-small" ng-model="panel.counter_pos" ng-options="f for f in ['above','below','none']"></select></span>
|
||||
</div>
|
||||
<div class="span3" ng-show="panel.chart != 'table' && panel.counter_pos != 'none'">
|
||||
<label class="small" >Legend Format</label>
|
||||
<select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
|
||||
</div>
|
||||
<div class="span1">
|
||||
<label class="small">Missing</label><input type="checkbox" ng-model="panel.missing" ng-checked="panel.missing">
|
||||
</div>
|
||||
<div class="span1">
|
||||
<label class="small">Other</label><input type="checkbox" ng-model="panel.other" ng-checked="panel.other">
|
||||
</div>
|
||||
<div class="span1" ng-show="panel.chart == 'pie'">
|
||||
<label class="small">Donut</label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
|
||||
</div>
|
||||
<div class="span1" ng-show="panel.chart == 'pie'">
|
||||
<label class="small">Tilt</label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
|
||||
</div>
|
||||
<div class="span1" ng-show="panel.chart == 'pie'">
|
||||
<label class="small">Labels</label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
|
||||
</div>
|
||||
</div>
|
@ -1,58 +0,0 @@
|
||||
<div ng-controller='terms' ng-init="init()">
|
||||
<style>
|
||||
.pieLabel { pointer-events: none }
|
||||
</style>
|
||||
<!-- START Pie or bar chart -->
|
||||
<div ng-show="panel.counter_pos == 'above' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
|
||||
<!-- vertical legend above -->
|
||||
<table class="small" ng-show="panel.arrangement == 'vertical'">
|
||||
<tr ng-repeat="term in legend">
|
||||
<td><i class="icon-circle" ng-style="{color:term.color}"></i></td> <td style="padding-right:10px;padding-left:10px;">{{term.label}}</td><td>{{term.data[0][1]}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- horizontal legend above -->
|
||||
<div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="term in legend" style="float:left;padding-left: 10px;">
|
||||
<span><i class="icon-circle" ng-style="{color:term.color}"></i> {{term.label}} ({{term.data[0][1]}}) </span>
|
||||
</div><br>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- keep legend from over lapping -->
|
||||
<div style="clear:both"></div>
|
||||
|
||||
<div ng-show="panel.chart == 'pie' || panel.chart == 'bar'" terms-chart params="{{panel}}" style="position:relative" class="pointer"></div>
|
||||
|
||||
<div ng-show="panel.counter_pos == 'below' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
|
||||
<!-- vertical legend below -->
|
||||
<table class="small" ng-show="panel.arrangement == 'vertical'">
|
||||
<tr ng-repeat="term in legend">
|
||||
<td><i class="icon-circle" ng-style="{color:term.color}"></i></i></td> <td style="padding-right:10px;padding-left:10px;">{{term.label}}</td><td>{{term.data[0][1]}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- horizontal legend below -->
|
||||
<div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="term in legend" style="float:left;padding-left: 10px;">
|
||||
<span><i class="icon-circle" ng-style="{color:term.color}"></i></span> {{term.label}} ({{term.data[0][1]}}) </span>
|
||||
</div><br>
|
||||
|
||||
</div>
|
||||
<!-- END Pie or Bar chart -->
|
||||
|
||||
<table ng-style="panel.style" class="table table-striped table-condensed" ng-show="panel.chart == 'table'">
|
||||
<thead>
|
||||
<th>Term</th> <th>Count</th> <th>Action</th>
|
||||
</thead>
|
||||
<tr ng-repeat="term in data" ng-show="showMeta(term)">
|
||||
<td>{{term.label}}</td>
|
||||
<td>{{term.data[0][1]}}</td>
|
||||
<td>
|
||||
<span ng-hide="term.meta == 'other'">
|
||||
<i class='icon-search pointer' ng-click="build_search(term)"></i>
|
||||
<i class='icon-ban-circle pointer' ng-click="build_search(term,true)"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
@ -1,358 +0,0 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/terms.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/terms/0
|
||||
* == terms
|
||||
* Status: *Stable*
|
||||
*
|
||||
* A table, bar chart or pie chart based on the results of an Elasticsearch terms facet.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'kbn'
|
||||
],
|
||||
function (angular, app, _, $, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.terms', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('terms', function($scope, querySrv, dashboard, filterSrv) {
|
||||
$scope.panelMeta = {
|
||||
modals : [
|
||||
{
|
||||
description: "Inspect",
|
||||
icon: "icon-info-sign",
|
||||
partial: "app/partials/inspector.html",
|
||||
show: $scope.panel.spyable
|
||||
}
|
||||
],
|
||||
editorTabs : [
|
||||
{title:'Queries', src:'app/partials/querySelect.html'}
|
||||
],
|
||||
status : "Stable",
|
||||
description : "Displays the results of an elasticsearch facet as a pie chart, bar chart, or a "+
|
||||
"table"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/terms/5
|
||||
* === Parameters
|
||||
*
|
||||
* field:: The field on which to computer the facet
|
||||
*/
|
||||
field : '_type',
|
||||
/** @scratch /panels/terms/5
|
||||
* exclude:: terms to exclude from the results
|
||||
*/
|
||||
exclude : [],
|
||||
/** @scratch /panels/terms/5
|
||||
* missing:: Set to false to disable the display of a counter showing how much results are
|
||||
* missing the field
|
||||
*/
|
||||
missing : true,
|
||||
/** @scratch /panels/terms/5
|
||||
* other:: Set to false to disable the display of a counter representing the aggregate of all
|
||||
* values outside of the scope of your +size+ property
|
||||
*/
|
||||
other : true,
|
||||
/** @scratch /panels/terms/5
|
||||
* size:: Show this many terms
|
||||
*/
|
||||
size : 10,
|
||||
/** @scratch /panels/terms/5
|
||||
* order:: count, term, reverse_count or reverse_term
|
||||
*/
|
||||
order : 'count',
|
||||
style : { "font-size": '10pt'},
|
||||
/** @scratch /panels/terms/5
|
||||
* donut:: In pie chart mode, draw a hole in the middle of the pie to make a tasty donut.
|
||||
*/
|
||||
donut : false,
|
||||
/** @scratch /panels/terms/5
|
||||
* tilt:: In pie chart mode, tilt the chart back to appear as more of an oval shape
|
||||
*/
|
||||
tilt : false,
|
||||
/** @scratch /panels/terms/5
|
||||
* lables:: In pie chart mode, draw labels in the pie slices
|
||||
*/
|
||||
labels : true,
|
||||
/** @scratch /panels/terms/5
|
||||
* arrangement:: In bar or pie mode, arrangement of the legend. horizontal or vertical
|
||||
*/
|
||||
arrangement : 'horizontal',
|
||||
/** @scratch /panels/terms/5
|
||||
* chart:: table, bar or pie
|
||||
*/
|
||||
chart : 'bar',
|
||||
/** @scratch /panels/terms/5
|
||||
* counter_pos:: The location of the legend in respect to the chart, above or below.
|
||||
*/
|
||||
counter_pos : 'above',
|
||||
/** @scratch /panels/terms/5
|
||||
* spyable:: Set spyable to false to disable the inspect button
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/terms/5
|
||||
* ==== Queries
|
||||
* queries object:: This object describes the queries to use on this panel.
|
||||
* queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
|
||||
* queries.ids::: In +selected+ mode, which query ids are selected.
|
||||
*/
|
||||
queries : {
|
||||
mode : 'all',
|
||||
ids : []
|
||||
},
|
||||
};
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.hits = 0;
|
||||
|
||||
$scope.$on('refresh',function(){
|
||||
$scope.get_data();
|
||||
});
|
||||
$scope.get_data();
|
||||
|
||||
};
|
||||
|
||||
$scope.get_data = function() {
|
||||
// Make sure we have everything for the request to complete
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
var request,
|
||||
results,
|
||||
boolQuery,
|
||||
queries;
|
||||
|
||||
request = $scope.ejs.Request().indices(dashboard.indices);
|
||||
|
||||
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
|
||||
queries = querySrv.getQueryObjs($scope.panel.queries.ids);
|
||||
|
||||
// This could probably be changed to a BoolFilter
|
||||
boolQuery = $scope.ejs.BoolQuery();
|
||||
_.each(queries,function(q) {
|
||||
boolQuery = boolQuery.should(querySrv.toEjsObj(q));
|
||||
});
|
||||
|
||||
|
||||
// Terms mode
|
||||
request = request
|
||||
.facet($scope.ejs.TermsFacet('terms')
|
||||
.field($scope.panel.field)
|
||||
.size($scope.panel.size)
|
||||
.order($scope.panel.order)
|
||||
.exclude($scope.panel.exclude)
|
||||
.facetFilter($scope.ejs.QueryFilter(
|
||||
$scope.ejs.FilteredQuery(
|
||||
boolQuery,
|
||||
filterSrv.getBoolFilter(filterSrv.ids)
|
||||
)))).size(0);
|
||||
|
||||
// Populate the inspector panel
|
||||
$scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
|
||||
|
||||
results = request.doSearch();
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
var k = 0;
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.hits = results.hits.total;
|
||||
$scope.data = [];
|
||||
_.each(results.facets.terms.terms, function(v) {
|
||||
var slice = { label : v.term, data : [[k,v.count]], actions: true};
|
||||
$scope.data.push(slice);
|
||||
k = k + 1;
|
||||
});
|
||||
|
||||
$scope.data.push({label:'Missing field',
|
||||
data:[[k,results.facets.terms.missing]],meta:"missing",color:'#aaa',opacity:0});
|
||||
$scope.data.push({label:'Other values',
|
||||
data:[[k+1,results.facets.terms.other]],meta:"other",color:'#444'});
|
||||
|
||||
$scope.$emit('render');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.build_search = function(term,negate) {
|
||||
if(_.isUndefined(term.meta)) {
|
||||
filterSrv.set({type:'terms',field:$scope.panel.field,value:term.label,
|
||||
mandate:(negate ? 'mustNot':'must')});
|
||||
} else if(term.meta === 'missing') {
|
||||
filterSrv.set({type:'exists',field:$scope.panel.field,
|
||||
mandate:(negate ? 'must':'mustNot')});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.set_refresh = function (state) {
|
||||
$scope.refresh = state;
|
||||
};
|
||||
|
||||
$scope.close_edit = function() {
|
||||
if($scope.refresh) {
|
||||
$scope.get_data();
|
||||
}
|
||||
$scope.refresh = false;
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.showMeta = function(term) {
|
||||
if(_.isUndefined(term.meta)) {
|
||||
return true;
|
||||
}
|
||||
if(term.meta === 'other' && !$scope.panel.other) {
|
||||
return false;
|
||||
}
|
||||
if(term.meta === 'missing' && !$scope.panel.missing) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('termsChart', function(querySrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function(){
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
var plot, chartData;
|
||||
|
||||
// IE doesn't work without this
|
||||
elem.css({height:scope.panel.height||scope.row.height});
|
||||
|
||||
// Make a clone we can operate on.
|
||||
chartData = _.clone(scope.data);
|
||||
chartData = scope.panel.missing ? chartData :
|
||||
_.without(chartData,_.findWhere(chartData,{meta:'missing'}));
|
||||
chartData = scope.panel.other ? chartData :
|
||||
_.without(chartData,_.findWhere(chartData,{meta:'other'}));
|
||||
|
||||
// Populate element.
|
||||
require(['jquery.flot.pie'], function(){
|
||||
// Populate element
|
||||
try {
|
||||
// Add plot to scope so we can build out own legend
|
||||
if(scope.panel.chart === 'bar') {
|
||||
plot = $.plot(elem, chartData, {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: { show: false, },
|
||||
bars: { show: true, fill: 1, barWidth: 0.8, horizontal: false },
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxis: { show: true, min: 0, color: "#c8c8c8" },
|
||||
xaxis: { show: false },
|
||||
grid: {
|
||||
borderWidth: 0,
|
||||
borderColor: '#eee',
|
||||
color: "#eee",
|
||||
hoverable: true,
|
||||
clickable: true
|
||||
},
|
||||
colors: querySrv.colors
|
||||
});
|
||||
}
|
||||
if(scope.panel.chart === 'pie') {
|
||||
var labelFormat = function(label, series){
|
||||
return '<div ng-click="build_search(panel.field,\''+label+'\')'+
|
||||
' "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
|
||||
label+'<br/>'+Math.round(series.percent)+'%</div>';
|
||||
};
|
||||
|
||||
plot = $.plot(elem, chartData, {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
pie: {
|
||||
innerRadius: scope.panel.donut ? 0.4 : 0,
|
||||
tilt: scope.panel.tilt ? 0.45 : 1,
|
||||
radius: 1,
|
||||
show: true,
|
||||
combine: {
|
||||
color: '#999',
|
||||
label: 'The Rest'
|
||||
},
|
||||
stroke: {
|
||||
width: 0
|
||||
},
|
||||
label: {
|
||||
show: scope.panel.labels,
|
||||
radius: 2/3,
|
||||
formatter: labelFormat,
|
||||
threshold: 0.1
|
||||
}
|
||||
}
|
||||
},
|
||||
//grid: { hoverable: true, clickable: true },
|
||||
grid: { hoverable: true, clickable: true },
|
||||
colors: querySrv.colors
|
||||
});
|
||||
}
|
||||
|
||||
// Populate legend
|
||||
if(elem.is(":visible")){
|
||||
setTimeout(function(){
|
||||
scope.legend = plot.getData();
|
||||
if(!scope.$$phase) {
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
elem.text(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
elem.bind("plotclick", function (event, pos, object) {
|
||||
if(object) {
|
||||
scope.build_search(scope.data[object.seriesIndex]);
|
||||
}
|
||||
});
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
if (item) {
|
||||
var value = scope.panel.chart === 'bar' ? item.datapoint[1] : item.datapoint[1][0][1];
|
||||
$tooltip
|
||||
.html(
|
||||
kbn.query_color_dot(item.series.color, 20) + ' ' +
|
||||
item.series.label + " (" + value.toFixed(0)+")"
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.remove();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user