mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge branch 'master' into issues/1726
This commit is contained in:
commit
d58d14d7fd
192 changed files with 4450 additions and 1632 deletions
|
@ -10,4 +10,5 @@ trim_trailing_whitespace = true
|
|||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
---
|
||||
parser: babel-eslint
|
||||
|
||||
plugins:
|
||||
- mocha
|
||||
|
||||
env:
|
||||
es6: true
|
||||
amd: true
|
||||
|
@ -67,3 +70,6 @@ rules:
|
|||
valid-typeof: 2
|
||||
wrap-iife: [ 2, outside ]
|
||||
yoda: 0
|
||||
|
||||
mocha/no-exclusive-tests: 2
|
||||
mocha/handle-done-callback: 2
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
language: node_js
|
||||
node_js: '0.12.7'
|
||||
install:
|
||||
- npm install -g npm@3.2
|
||||
- npm install
|
||||
script: ./node_modules/.bin/grunt travis
|
||||
sudo: false
|
||||
addons:
|
||||
firefox: "40.0"
|
||||
cache:
|
||||
directories:
|
||||
- esvm
|
||||
|
|
|
@ -23,12 +23,6 @@ Please make sure you have signed the [Contributor License Agreement](http://www.
|
|||
nvm install "$(cat .node-version)"
|
||||
```
|
||||
|
||||
- Install npm 3.2
|
||||
|
||||
```sh
|
||||
npm install -g npm@3.2
|
||||
```
|
||||
|
||||
- Install dependencies
|
||||
|
||||
```sh
|
||||
|
@ -44,7 +38,7 @@ Please make sure you have signed the [Contributor License Agreement](http://www.
|
|||
- Start the development server.
|
||||
|
||||
```sh
|
||||
./bin/kibana --dev
|
||||
npm start
|
||||
```
|
||||
|
||||
#### `config/kibana.dev.yml`
|
||||
|
@ -73,6 +67,7 @@ Here are some hints for getting eslint setup in your favorite editor:
|
|||
| IntelliJ | Settings » Languages & Frameworks » JavaScript » Code Quality Tools » ESLint |
|
||||
| vi | [scrooloose/syntastic](https://github.com/scrooloose/syntastic) |
|
||||
|
||||
Another tool we use for enforcing consistent coding style is Editorconfig, which can be set up by installing a plugin in your editor that dynamically updates its configuration. Take a look at the [Editorconfig](http://editorconfig.org/#download) site to find a plugin for your editor, and browse our [`.editorconfig`](https://github.com/elastic/kibana/blob/master/.editorconfig) file to see what config rules we set up.
|
||||
|
||||
### Testing and building
|
||||
|
||||
|
|
17
Gruntfile.js
17
Gruntfile.js
|
@ -45,7 +45,22 @@ module.exports = function (grunt) {
|
|||
'<%= root %>/tasks/**/*.js',
|
||||
'<%= src %>/**/*.js',
|
||||
'!<%= src %>/fixtures/**/*.js'
|
||||
]
|
||||
],
|
||||
deepModules: {
|
||||
'caniuse-db': '1.0.30000265',
|
||||
'chalk': '1.1.0',
|
||||
'glob': '4.5.3',
|
||||
'har-validator': '1.8.0',
|
||||
'json5': '0.4.0',
|
||||
'loader-utils': '0.2.11',
|
||||
'micromatch': '2.2.0',
|
||||
'postcss-normalize-url': '2.1.1',
|
||||
'postcss-reduce-idents': '1.0.2',
|
||||
'postcss-unique-selectors': '1.0.0',
|
||||
'postcss-minify-selectors': '1.4.6',
|
||||
'postcss-single-charset': '0.3.0',
|
||||
'regenerator': '0.8.36'
|
||||
}
|
||||
};
|
||||
|
||||
grunt.config.merge(config);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Kibana 4.2.0-snapshot
|
||||
# Kibana 4.3.0-snapshot
|
||||
|
||||
[](https://travis-ci.org/elastic/kibana?branch=master)
|
||||
|
||||
|
|
|
@ -25,7 +25,11 @@
|
|||
# elasticsearch.username: user
|
||||
# elasticsearch.password: pass
|
||||
|
||||
# If your Elasticsearch requires client certificate and key
|
||||
# SSL for outgoing requests from the Kibana Server to the browser (PEM formatted)
|
||||
# server.ssl.cert: /path/to/your/server.crt
|
||||
# server.ssl.key: /path/to/your/server.key
|
||||
|
||||
# Optional setting to validate that your Elasticsearch backend uses the same key files (PEM formatted)
|
||||
# elasticsearch.ssl.cert: /path/to/your/client.crt
|
||||
# elasticsearch.ssl.key: /path/to/your/client.key
|
||||
|
||||
|
@ -52,12 +56,17 @@
|
|||
# Time in milliseconds to wait for Elasticsearch at Kibana startup before retrying
|
||||
# elasticsearch.startupTimeout: 5000
|
||||
|
||||
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
||||
# server.ssl.cert: /path/to/your/server.crt
|
||||
# server.ssl.key: /path/to/your/server.key
|
||||
|
||||
# Set the path to where you would like the process id file to be created.
|
||||
# pid.file: /var/run/kibana.pid
|
||||
|
||||
# If you would like to send the log output to a file you can set the path below.
|
||||
# logging.dest: stdout
|
||||
|
||||
# Set this to true to suppress all logging output.
|
||||
# logging.silent: false
|
||||
|
||||
# Set this to true to suppress all logging output except for error messages.
|
||||
# logging.quiet: false
|
||||
|
||||
# Set this to true to log all events, including system usage information and all requests.
|
||||
# logging.verbose
|
||||
|
|
|
@ -19,9 +19,14 @@ You need at least one saved <<visualize, visualization>> to use a dashboard.
|
|||
|
||||
The first time you click the *Dashboard* tab, Kibana displays an empty dashboard.
|
||||
|
||||
image:images/NewDashboard.jpg[New Dashboard screen]
|
||||
image:images/NewDashboard.png[New Dashboard screen]
|
||||
|
||||
Build your dashboard by adding visualizations.
|
||||
Build your dashboard by adding visualizations. By default, Kibana dashboards use a light color theme. To use a dark color
|
||||
theme instead, click the *Settings* image:images/SettingsButton.jpg[Gear] button and check the *Use dark theme* box.
|
||||
|
||||
image:images/darktheme.png[Dark Theme Example]
|
||||
|
||||
NOTE: You can change the default theme in the *Advanced* section of the *Settings* tab.
|
||||
|
||||
[float]
|
||||
[[dash-autorefresh]]
|
||||
|
@ -67,8 +72,7 @@ in your Web page.
|
|||
NOTE: A user must have Kibana access in order to view embedded dashboards.
|
||||
|
||||
Click the *Share* button to display HTML code to embed the dashboard in another Web page, along with a direct link to
|
||||
the dashboard. Click the copy button image:images/Clipboard.png[Copy to Clipboard button] next to either option to copy
|
||||
the code or the link to your clipboard.
|
||||
the dashboard. You can select the text in either option to copy the code or the link to your clipboard.
|
||||
|
||||
[float]
|
||||
[[embedding-dashboards]]
|
||||
|
|
BIN
docs/images/NewDashboard.png
Normal file
BIN
docs/images/NewDashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
docs/images/darktheme.png
Normal file
BIN
docs/images/darktheme.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 196 KiB |
|
@ -11,6 +11,8 @@ include::setup.asciidoc[]
|
|||
|
||||
include::getting-started.asciidoc[]
|
||||
|
||||
include::plugins.asciidoc[]
|
||||
|
||||
include::access.asciidoc[]
|
||||
|
||||
include::discover.asciidoc[]
|
||||
|
|
122
docs/plugins.asciidoc
Normal file
122
docs/plugins.asciidoc
Normal file
|
@ -0,0 +1,122 @@
|
|||
[[kibana-plugins]]
|
||||
== Kibana Plugins
|
||||
|
||||
Add-on functionality for Kibana is implemented with plug-in modules. You can use the `bin/kibana plugin`
|
||||
command to manage these modules. You can also install a plugin manually by moving the plugin file to the
|
||||
`installedPlugins` directory and unpacking the plugin files into a new directory.
|
||||
|
||||
[float]
|
||||
=== Installing Plugins
|
||||
|
||||
Use the following command to install a plugin:
|
||||
|
||||
[source,shell]
|
||||
bin/kibana plugin --install <org>/<package>/<version>
|
||||
|
||||
You can also use `-i` instead of `--install`, as in the following example:
|
||||
|
||||
[source,shell]
|
||||
bin/kibana plugin -i elasticsearch/marvel-ui/latest
|
||||
|
||||
Because the organization given is `elasticsearch`, the plugin management tool automatically downloads the
|
||||
plugin from `download.elastic.co`.
|
||||
|
||||
[float]
|
||||
=== Installing Plugins from Github
|
||||
|
||||
When the specified plugin is not found at `download.elastic.co`, the plugin management tool parses the element
|
||||
as a Github user name, as in the following example:
|
||||
|
||||
[source,shell]
|
||||
bin/kibana plugin --install github-user/sample-plugin
|
||||
Installing sample-plugin
|
||||
Attempting to extract from https://download.elastic.co/github-user/sample-plugin/sample-plugin-latest.tar.gz
|
||||
Attempting to extract from https://github.com/github-user/sample-plugin/archive/master.tar.gz
|
||||
Downloading <some number> bytes....................
|
||||
Extraction complete
|
||||
Optimizing and caching browser bundles...
|
||||
Plugin installation complete
|
||||
|
||||
[float]
|
||||
=== Installing Plugins from an Arbitrary URL
|
||||
|
||||
You can specify a URL to a plugin with the `-u` or `--url` options after the `-i` or `--install` option, as in the
|
||||
following example:
|
||||
|
||||
[source,shell]
|
||||
bin/kibana plugin -i sample-plugin -u https://some.sample.url/directory
|
||||
Installing sample-plugin
|
||||
Attempting to extract from https://some.sample.url/directory
|
||||
Downloading <some number> bytes....................
|
||||
Extraction complete
|
||||
Optimizing and caching browser bundles...
|
||||
Plugin installation complete
|
||||
|
||||
You can specify URLs that use the HTTP, HTTPS, or `file` protocols.
|
||||
|
||||
[float]
|
||||
=== Installing Plugins to an Arbitrary Directory
|
||||
|
||||
Use the `-d` or `--plugin-dir` option to specify a directory for plugins, as in the following example:
|
||||
|
||||
[source,shell]
|
||||
bin/kibana plugin -i elasticsearch/sample-plugin/latest -d <path/to/directory>
|
||||
Installing sample-plugin
|
||||
Attempting to extract from https://download.elastic.co/elasticsearch/sample-plugin/sample-plugin-latest.tar.gz
|
||||
Downloading <some number> bytes....................
|
||||
Extraction complete
|
||||
Optimizing and caching browser bundles...
|
||||
Plugin installation complete
|
||||
|
||||
NOTE: This command creates the specified directory if it does not already exist.
|
||||
|
||||
[float]
|
||||
=== Removing Plugins
|
||||
|
||||
Use the `--remove` or `-r` option to remove a plugin, including any configuration information, as in the following
|
||||
example:
|
||||
|
||||
[source,shell]
|
||||
bin/kibana plugin --remove marvel-ui
|
||||
|
||||
You can also remove a plugin manually by deleting the plugin's subdirectory under the `installedPlugins` directory.
|
||||
|
||||
[float]
|
||||
=== Updating Plugins
|
||||
|
||||
To update a plugin, remove the current version and reinstall the plugin.
|
||||
|
||||
[float]
|
||||
=== Configuring the Plugin Manager
|
||||
|
||||
By default, the plugin manager provides you with feedback on the status of the activity you've asked the plugin manager
|
||||
to perform. You can control the level of feedback with the `--quiet` and `--silent` options. Use the `--quiet` option to
|
||||
suppress all non-error output. Use the `--silent` option to suppress all output.
|
||||
|
||||
By default, plugin manager requests do not time out. Use the `--timeout` option, followed by a time, to change this
|
||||
behavior, as in the following examples:
|
||||
|
||||
[source,shell]
|
||||
.Waits for 30 seconds before failing
|
||||
bin/kibana plugin --install username/sample-plugin --timeout 30s
|
||||
|
||||
[source,shell]
|
||||
.Waits for 1 minute before failing
|
||||
bin/kibana plugin --install username/sample-plugin --timeout 1m
|
||||
|
||||
[float]
|
||||
==== Plugins and Custom Kibana Configurations
|
||||
|
||||
Use the `-c` or `--config` options to specify the path to the configuration file used to start Kibana. By default, Kibana
|
||||
uses the configuration file `config/kibana.yml`. When you change your installed plugins, the `bin/kibana plugin` command
|
||||
restarts the Kibana server. When you are using a customized configuration file, you must specify the
|
||||
path to that configuration file each time you use the `bin/kibana plugin` command.
|
||||
|
||||
[float]
|
||||
=== Plugin Manager Exit Codes
|
||||
|
||||
[horizontal]
|
||||
0:: Success
|
||||
64:: Unknown command or incorrect option parameter
|
||||
74:: I/O error
|
||||
70:: Other error
|
|
@ -107,6 +107,18 @@ heatmap dots appear at full intensity.
|
|||
* *Show Tooltip*: Check this box to have a tooltip with the values for a given dot when the cursor is on that dot.
|
||||
|
||||
*Desaturate map tiles*:: Desaturate the map's color in order to make the markers stand out more clearly.
|
||||
*WMS compliant map server*:: Check this box to enable the use of a third-party mapping service that complies with the Web
|
||||
Map Service (WMS) standard. Specify the following elements:
|
||||
|
||||
* *WMS url*: The URL for the WMS map service.
|
||||
* *WMS layers*: A comma-separated list of the layers to use in this visualization. Each map server provides its own list of
|
||||
layers.
|
||||
* *WMS version*: The WMS version used by this map service.
|
||||
* *WMS format*: The image format used by this map service. The two most common formats are `image/png` and `image/jpeg`.
|
||||
* *WMS attribution*: An optional, user-defined string that identifies the map source. Maps display the attribution string
|
||||
in the lower right corner.
|
||||
* *WMS styles*: A comma-separated list of the styles to use in this visualization. Each map server provides its own styling
|
||||
options.
|
||||
|
||||
After changing options, click the green *Apply changes* button to update your visualization, or the grey *Discard
|
||||
changes* button to keep your visualization in its current state.
|
||||
|
|
221
package.json
221
package.json
|
@ -11,7 +11,7 @@
|
|||
"dashboarding"
|
||||
],
|
||||
"private": false,
|
||||
"version": "4.2.0-snapshot",
|
||||
"version": "4.3.0-snapshot",
|
||||
"build": {
|
||||
"number": 8467,
|
||||
"sha": "6cb7fec4e154faa0a4a3fee4b33dfef91b9870d9"
|
||||
|
@ -43,132 +43,133 @@
|
|||
"start": "./bin/kibana --dev",
|
||||
"precommit": "grunt lintStagedFiles",
|
||||
"karma": "karma start",
|
||||
"elasticsearch": "grunt esvm:dev:keepalive"
|
||||
"elasticsearch": "grunt esvm:dev:keepalive",
|
||||
"lint": "grunt eslint:source",
|
||||
"lintroller": "grunt eslint:fixSource"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/elastic/kibana.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@spalger/angular-bootstrap": "^0.10.0",
|
||||
"@spalger/angular-nvd3": "^1.0.0-beta",
|
||||
"@spalger/filesaver": "^1.1.2",
|
||||
"@spalger/leaflet-draw": "^0.2.3",
|
||||
"@spalger/leaflet-heat": "^0.1.3",
|
||||
"@spalger/nvd3": "^1.8.1",
|
||||
"@spalger/ui-ace": "^0.2.3",
|
||||
"@spalger/angular-bootstrap": "0.10.0",
|
||||
"@spalger/angular-nvd3": "1.0.0-beta",
|
||||
"@spalger/filesaver": "1.1.2",
|
||||
"@spalger/leaflet-draw": "0.2.3",
|
||||
"@spalger/leaflet-heat": "0.1.3",
|
||||
"@spalger/numeral": "^2.0.0",
|
||||
"@spalger/nvd3": "1.8.1",
|
||||
"@spalger/ui-ace": "0.2.3",
|
||||
"angular": "1.2.28",
|
||||
"angular-bindonce": "0.3.1",
|
||||
"angular-bootstrap-colorpicker": "^3.0.18",
|
||||
"angular-elastic": "2.5.0",
|
||||
"angular-route": "1.2.28",
|
||||
"ansicolors": "^0.3.2",
|
||||
"autoprefixer": "5.1.x",
|
||||
"autoprefixer-loader": "2.0.x",
|
||||
"babel": "^5.8.21",
|
||||
"babel-core": "^5.8.22",
|
||||
"babel-loader": "^5.3.2",
|
||||
"babel-runtime": "^5.8.20",
|
||||
"bluebird": "^2.9.27",
|
||||
"boom": "^2.8.0",
|
||||
"bootstrap": "^3.3.5",
|
||||
"brace": "^0.5.1",
|
||||
"bunyan": "^1.2.3",
|
||||
"commander": "^2.8.1",
|
||||
"css-loader": "^0.15.1",
|
||||
"d3": "^3.5.6",
|
||||
"elasticsearch": "^8.0.1",
|
||||
"elasticsearch-browser": "^8.0.1",
|
||||
"expiry-js": "^0.1.7",
|
||||
"exports-loader": "^0.6.2",
|
||||
"expose-loader": "^0.7.0",
|
||||
"extract-text-webpack-plugin": "^0.8.2",
|
||||
"file-loader": "^0.8.4",
|
||||
"font-awesome": "^4.3.0",
|
||||
"good": "^6.2.0",
|
||||
"good-squeeze": "^2.1.0",
|
||||
"gridster": "^0.5.6",
|
||||
"hapi": "^8.6.1",
|
||||
"imports-loader": "^0.6.4",
|
||||
"jade": "^1.7.2",
|
||||
"jade-loader": "^0.7.1",
|
||||
"joi": "^6.4.3",
|
||||
"jquery": "^2.1.4",
|
||||
"js-yaml": "^3.2.5",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"jstimezonedetect": "^1.0.5",
|
||||
"leaflet": "^0.7.3",
|
||||
"less": "^2.5.1",
|
||||
"less-loader": "^2.2.0",
|
||||
"lodash": "^3.10.0",
|
||||
"ansicolors": "0.3.2",
|
||||
"autoprefixer": "5.1.1",
|
||||
"autoprefixer-loader": "2.0.0",
|
||||
"babel": "5.8.23",
|
||||
"babel-core": "5.8.23",
|
||||
"babel-loader": "5.3.2",
|
||||
"babel-runtime": "5.8.20",
|
||||
"bluebird": "2.9.34",
|
||||
"boom": "2.8.0",
|
||||
"bootstrap": "3.3.5",
|
||||
"brace": "0.5.1",
|
||||
"bunyan": "1.4.0",
|
||||
"commander": "2.8.1",
|
||||
"css-loader": "0.17.0",
|
||||
"d3": "3.5.6",
|
||||
"elasticsearch": "8.0.1",
|
||||
"elasticsearch-browser": "8.0.1",
|
||||
"expiry-js": "0.1.7",
|
||||
"exports-loader": "0.6.2",
|
||||
"expose-loader": "0.7.0",
|
||||
"extract-text-webpack-plugin": "0.8.2",
|
||||
"file-loader": "0.8.4",
|
||||
"font-awesome": "4.4.0",
|
||||
"good": "6.3.0",
|
||||
"good-squeeze": "2.1.0",
|
||||
"gridster": "0.5.6",
|
||||
"hapi": "8.8.1",
|
||||
"imports-loader": "0.6.4",
|
||||
"jade": "1.11.0",
|
||||
"jade-loader": "0.7.1",
|
||||
"joi": "6.6.1",
|
||||
"jquery": "2.1.4",
|
||||
"js-yaml": "3.4.1",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jstimezonedetect": "1.0.5",
|
||||
"leaflet": "0.7.5",
|
||||
"less": "2.5.1",
|
||||
"less-loader": "2.2.0",
|
||||
"lodash": "3.10.1",
|
||||
"marked": "0.3.3",
|
||||
"minimatch": "^2.0.8",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment": "^2.10.3",
|
||||
"ng-clip": "^0.2.6",
|
||||
"numeral": "^1.5.3",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.60.0",
|
||||
"requirefrom": "^0.2.0",
|
||||
"rimraf": "^2.4.1",
|
||||
"rjs-repack-loader": "^1.0.6",
|
||||
"script-loader": "^0.6.1",
|
||||
"semver": "^4.3.6",
|
||||
"style-loader": "^0.12.3",
|
||||
"tar": "^2.1.1",
|
||||
"url-loader": "^0.5.6",
|
||||
"webpack": "^1.10.0",
|
||||
"webpack-directory-name-as-main": "^1.0.0",
|
||||
"whatwg-fetch": "^0.9.0",
|
||||
"zeroclipboard": "^2.2.0"
|
||||
"minimatch": "2.0.10",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "2.10.6",
|
||||
"raw-loader": "0.5.1",
|
||||
"request": "2.61.0",
|
||||
"requirefrom": "0.2.0",
|
||||
"rimraf": "2.4.3",
|
||||
"rjs-repack-loader": "1.0.6",
|
||||
"script-loader": "0.6.1",
|
||||
"semver": "4.3.6",
|
||||
"style-loader": "0.12.3",
|
||||
"tar": "2.2.0",
|
||||
"url-loader": "0.5.6",
|
||||
"webpack": "1.12.1",
|
||||
"webpack-directory-name-as-main": "1.0.0",
|
||||
"whatwg-fetch": "0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"Nonsense": "^0.1.2",
|
||||
"Nonsense": "0.1.2",
|
||||
"angular-mocks": "1.2.28",
|
||||
"auto-release-sinon": "^1.0.3",
|
||||
"babel-eslint": "^4.1.1",
|
||||
"chokidar": "^1.0.4",
|
||||
"eslint": "^1.3.1",
|
||||
"expect.js": "^0.3.1",
|
||||
"faker": "^1.1.0",
|
||||
"glob": "^4.3.2",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-babel": "^5.0.1",
|
||||
"auto-release-sinon": "1.0.3",
|
||||
"babel-eslint": "4.1.3",
|
||||
"chokidar": "1.0.5",
|
||||
"eslint": "1.5.1",
|
||||
"eslint-plugin-mocha": "1.0.0",
|
||||
"expect.js": "0.3.1",
|
||||
"faker": "1.1.0",
|
||||
"glob": "4.5.3",
|
||||
"grunt": "0.4.5",
|
||||
"grunt-babel": "5.0.1",
|
||||
"grunt-cli": "0.1.13",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-esvm": "^1.1.5",
|
||||
"grunt-karma": "^0.12.0",
|
||||
"grunt-run": "^0.4.0",
|
||||
"grunt-s3": "^0.2.0-alpha.3",
|
||||
"grunt-simple-mocha": "^0.4.0",
|
||||
"gruntify-eslint": "^1.0.1",
|
||||
"html-entities": "^1.1.1",
|
||||
"husky": "^0.8.1",
|
||||
"istanbul-instrumenter-loader": "^0.1.3",
|
||||
"karma": "^0.13.3",
|
||||
"karma-chrome-launcher": "^0.2.0",
|
||||
"karma-coverage": "^0.5.0",
|
||||
"karma-firefox-launcher": "^0.1.6",
|
||||
"karma-growl-reporter": "^0.1.1",
|
||||
"karma-ie-launcher": "^0.2.0",
|
||||
"karma-mocha": "^0.2.0",
|
||||
"karma-safari-launcher": "^0.1.1",
|
||||
"libesvm": "^1.0.1",
|
||||
"license-checker": "^3.1.0",
|
||||
"load-grunt-config": "^0.7.0",
|
||||
"marked-text-renderer": "^0.1.0",
|
||||
"mocha": "^2.2.5",
|
||||
"nock": "^2.9.0",
|
||||
"npm": "3.2",
|
||||
"portscanner": "^1.0.0",
|
||||
"simple-git": "^1.3.0",
|
||||
"sinon": "^1.15.4",
|
||||
"source-map": "^0.4.4",
|
||||
"wreck": "^6.1.0"
|
||||
"grunt-contrib-clean": "0.6.0",
|
||||
"grunt-contrib-copy": "0.8.1",
|
||||
"grunt-esvm": "1.1.6",
|
||||
"grunt-karma": "0.12.0",
|
||||
"grunt-run": "0.4.0",
|
||||
"grunt-s3": "0.2.0-alpha.3",
|
||||
"grunt-simple-mocha": "0.4.0",
|
||||
"gruntify-eslint": "1.0.1",
|
||||
"html-entities": "1.1.3",
|
||||
"husky": "0.8.1",
|
||||
"istanbul-instrumenter-loader": "0.1.3",
|
||||
"karma": "0.13.9",
|
||||
"karma-chrome-launcher": "0.2.0",
|
||||
"karma-coverage": "0.5.1",
|
||||
"karma-firefox-launcher": "0.1.6",
|
||||
"karma-growl-reporter": "0.1.1",
|
||||
"karma-ie-launcher": "0.2.0",
|
||||
"karma-mocha": "0.2.0",
|
||||
"karma-safari-launcher": "0.1.1",
|
||||
"libesvm": "1.0.7",
|
||||
"license-checker": "3.1.0",
|
||||
"load-grunt-config": "0.7.2",
|
||||
"marked-text-renderer": "0.1.0",
|
||||
"mocha": "2.3.0",
|
||||
"nock": "2.10.0",
|
||||
"npm": "2.11.0",
|
||||
"portscanner": "1.0.0",
|
||||
"simple-git": "1.8.0",
|
||||
"sinon": "1.16.1",
|
||||
"source-map": "0.4.4",
|
||||
"wreck": "6.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "2.5",
|
||||
"npm": "3.2"
|
||||
"node": "0.12",
|
||||
"npm": "2.14.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ ${help(this, ' ')}
|
|||
};
|
||||
|
||||
Command.prototype.unknownArgv = function (argv) {
|
||||
if (argv) this.__unkownArgv = argv;
|
||||
return this.__unkownArgv ? this.__unkownArgv.slice(0) : [];
|
||||
if (argv) this.__unknownArgv = argv;
|
||||
return this.__unknownArgv ? this.__unknownArgv.slice(0) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,7 @@ program
|
|||
.description('Get the help for a specific command')
|
||||
.action(function (cmdName) {
|
||||
var cmd = _.find(program.commands, { _name: cmdName });
|
||||
if (!cmd) return this.error(`unknown command ${cmd}`);
|
||||
if (!cmd) return this.error(`unknown command ${cmdName}`);
|
||||
cmd.help();
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,11 @@ module.exports = function (program) {
|
|||
.option('-q, --quiet', 'Disable all process messaging except errors')
|
||||
.option('-s, --silent', 'Disable all process messaging')
|
||||
.option('-u, --url <url>', 'Specify download url')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'Path to the config file',
|
||||
fromRoot('config/kibana.yml')
|
||||
)
|
||||
.option(
|
||||
'-t, --timeout <duration>',
|
||||
'Length of time before failing; 0 for never fail',
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
let _ = require('lodash');
|
||||
var utils = require('requirefrom')('src/utils');
|
||||
var fromRoot = utils('fromRoot');
|
||||
var pluginDownloader = require('./pluginDownloader');
|
||||
var pluginCleaner = require('./pluginCleaner');
|
||||
var KbnServer = require('../../server/KbnServer');
|
||||
var readYamlConfig = require('../serve/readYamlConfig');
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
|
@ -25,7 +30,36 @@ function install(settings, logger) {
|
|||
.then(function () {
|
||||
return downloader.download();
|
||||
})
|
||||
.then(function (curious) {
|
||||
.then(async function() {
|
||||
logger.log('Optimizing and caching browser bundles...');
|
||||
let serverConfig = _.merge(
|
||||
readYamlConfig(settings.config),
|
||||
{
|
||||
env: 'production',
|
||||
logging: {
|
||||
silent: settings.silent,
|
||||
quiet: !settings.silent,
|
||||
verbose: false
|
||||
},
|
||||
optimize: {
|
||||
useBundleCache: false
|
||||
},
|
||||
server: {
|
||||
autoListen: false
|
||||
},
|
||||
plugins: {
|
||||
initialize: false,
|
||||
scanDirs: [settings.pluginDir, fromRoot('src/plugins')],
|
||||
paths: [settings.workingPath]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let kbnServer = new KbnServer(serverConfig);
|
||||
await kbnServer.ready();
|
||||
await kbnServer.close();
|
||||
})
|
||||
.then(function () {
|
||||
fs.renameSync(settings.workingPath, settings.pluginPath);
|
||||
logger.log('Plugin installation complete');
|
||||
})
|
||||
|
|
|
@ -54,6 +54,10 @@ module.exports = function (options) {
|
|||
settings.urls.push(options.url);
|
||||
}
|
||||
|
||||
if (options.config) {
|
||||
settings.config = options.config;
|
||||
}
|
||||
|
||||
if (options.install) {
|
||||
settings.action = 'install';
|
||||
parts = options.install.split('/');
|
||||
|
|
|
@ -32,7 +32,10 @@ module.exports = function (program) {
|
|||
.description('Run the kibana server')
|
||||
.collectUnknownOptions()
|
||||
.option('-e, --elasticsearch <uri>', 'Elasticsearch instance')
|
||||
.option('-c, --config <path>', 'Path to the config file')
|
||||
.option(
|
||||
'-c, --config <path>',
|
||||
'Path to the config file',
|
||||
fromRoot('config/kibana.yml'))
|
||||
.option('-p, --port <port>', 'The port to bind to', parseInt)
|
||||
.option('-q, --quiet', 'Prevent all logging except errors')
|
||||
.option('-Q, --silent', 'Prevent all logging')
|
||||
|
@ -76,7 +79,7 @@ module.exports = function (program) {
|
|||
let readYamlConfig = require('./readYamlConfig');
|
||||
let KbnServer = src('server/KbnServer');
|
||||
|
||||
let settings = readYamlConfig(opts.config || fromRoot('config/kibana.yml'));
|
||||
let settings = readYamlConfig(opts.config);
|
||||
|
||||
if (opts.dev) {
|
||||
try { _.merge(settings, readYamlConfig(fromRoot('config/kibana.dev.yml'))); }
|
||||
|
|
|
@ -85,7 +85,6 @@ class BaseOptimizer {
|
|||
new DirectoryNameAsMain()
|
||||
]),
|
||||
new webpack.NoErrorsPlugin(),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
new ExtractTextPlugin('[name].style.css', {
|
||||
allChunks: true
|
||||
}),
|
||||
|
@ -101,7 +100,7 @@ class BaseOptimizer {
|
|||
test: /\.less$/,
|
||||
loader: ExtractTextPlugin.extract(
|
||||
'style',
|
||||
`css${mapQ}!autoprefixer?{ "browsers": ["last 2 versions","> 5%"] }!less${mapQ}`
|
||||
`css${mapQ}!autoprefixer${mapQ ? mapQ + '&' : '?'}{ "browsers": ["last 2 versions","> 5%"] }!less${mapQ}`
|
||||
)
|
||||
},
|
||||
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', `css${mapQ}`) },
|
||||
|
|
|
@ -19,7 +19,9 @@ module.exports = async (kbnServer, server, config) => {
|
|||
await bundles.writeEntryFiles();
|
||||
|
||||
// in prod, only bundle what looks invalid or missing
|
||||
if (config.get('env.prod')) bundles = await kbnServer.bundles.getInvalidBundles();
|
||||
if (config.get('optimize.useBundleCache')) {
|
||||
bundles = await bundles.getInvalidBundles();
|
||||
}
|
||||
|
||||
// we might not have any work to do
|
||||
if (!bundles.getIds().length) {
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = async (kbnServer, server, config) => {
|
|||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`unkown kbnWorkerType "${process.env.kbnWorkerType}"`);
|
||||
throw new Error(`unknown kbnWorkerType "${process.env.kbnWorkerType}"`);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
module.exports = function (kibana) {
|
||||
return new kibana.Plugin({
|
||||
uiExports: {
|
||||
app: {
|
||||
id: 'appSwitcher',
|
||||
main: 'plugins/appSwitcher/appSwitcher',
|
||||
hidden: true,
|
||||
autoload: kibana.autoload.styles
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "appSwitcher",
|
||||
"version": "1.0.0"
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<div ng-if="switcher.loading">
|
||||
<div class="spinner large"></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="!switcher.loading" class="app-links">
|
||||
<a
|
||||
ng-repeat="app in switcher.apps"
|
||||
ng-href="/app/{{app.id}}"
|
||||
class="app-link">
|
||||
<span ng-style="{ 'background-image': 'url(' + app.icon + ')' }" class="app-icon"></span>
|
||||
<span class="app-info">
|
||||
<h2 class="app-title">{{ app.title }}</h2>
|
||||
<h3 class="app-description">{{ app.description }}</h3>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
|
@ -1,31 +0,0 @@
|
|||
require('plugins/appSwitcher/appSwitcher.less');
|
||||
|
||||
var kibanaLogoUrl = require('ui/images/kibana.png');
|
||||
|
||||
require('ui/chrome')
|
||||
.setBrand({
|
||||
'logo': 'url(' + kibanaLogoUrl + ') left no-repeat',
|
||||
'smallLogo': 'url(' + kibanaLogoUrl + ') left no-repeat'
|
||||
})
|
||||
.setShowAppsLink(false)
|
||||
.setTabs([
|
||||
{
|
||||
id: '',
|
||||
title: 'Apps',
|
||||
activeIndicatorColor: '#ecf0f1'
|
||||
}
|
||||
])
|
||||
.setRootTemplate(require('plugins/appSwitcher/appSwitcher.html'))
|
||||
.setRootController('switcher', function SwitcherController($http) {
|
||||
var switcher = {
|
||||
loading: true
|
||||
};
|
||||
|
||||
$http.get('/api/apps')
|
||||
.then(function (resp) {
|
||||
switcher.loading = false;
|
||||
switcher.apps = resp.data;
|
||||
});
|
||||
|
||||
return switcher;
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
.application {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
|
||||
.app-links {
|
||||
width: 700px;
|
||||
margin: 25px auto;
|
||||
text-align: justify;
|
||||
|
||||
.app-link {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
background: white;
|
||||
width: 200px;
|
||||
margin: 0 30px 30px 0;
|
||||
|
||||
.app-icon {
|
||||
display: block;
|
||||
height: 200px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.app-info {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
margin: 0 0 10px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.app-description {
|
||||
font-size: 1em;
|
||||
color: @gray;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
<div class="form-group">
|
||||
<label>
|
||||
Vis State
|
||||
<kbn-clipboard copy="visStateJson"></kbn-clipboard>
|
||||
</label>
|
||||
<pre>{{visStateJson}}</pre>
|
||||
</div>
|
||||
|
|
|
@ -3,8 +3,6 @@ define(function (require) {
|
|||
require('ui/registry/spy_modes').register(VisDetailsSpyProvider);
|
||||
|
||||
function VisDetailsSpyProvider(Notifier, $filter, $rootScope, config) {
|
||||
require('ui/clipboard');
|
||||
|
||||
return {
|
||||
name: 'debug',
|
||||
display: 'Debug',
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
var portscanner = require('portscanner');
|
||||
var path = require('path');
|
||||
var Promise = require('bluebird');
|
||||
var libesvm = require('libesvm');
|
||||
var fromRoot = require('requirefrom')('src/utils')('fromRoot');
|
||||
|
||||
function startEs() {
|
||||
var options = {
|
||||
branch: 'master',
|
||||
directory: fromRoot('esvm/test-es'),
|
||||
purge: true,
|
||||
config: {
|
||||
'cluster.name': 'test',
|
||||
'network.host': '127.0.0.1'
|
||||
}
|
||||
};
|
||||
var cluster = libesvm.createCluster(options);
|
||||
return cluster.install().then(function () {
|
||||
return cluster.start();
|
||||
}).then(function () {
|
||||
after(function () {
|
||||
this.timeout(120000);
|
||||
return cluster.shutdown();
|
||||
});
|
||||
return cluster;
|
||||
});
|
||||
}
|
||||
|
||||
function maybeStartES() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
portscanner.checkPortStatus(9200, '127.0.0.1', function (err, status) {
|
||||
if (err) return reject(err);
|
||||
if (status === 'closed') return startEs().then(resolve, reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
this.timeout(120000);
|
||||
return maybeStartES();
|
||||
};
|
|
@ -8,7 +8,6 @@ var fromRoot = src('utils/fromRoot');
|
|||
|
||||
describe('plugins/elasticsearch', function () {
|
||||
describe('routes', function () {
|
||||
before(require('./_ensure_elasticsearch'));
|
||||
|
||||
var kbnServer;
|
||||
|
||||
|
|
20
src/plugins/elasticsearch/lib/call_with_request.js
Normal file
20
src/plugins/elasticsearch/lib/call_with_request.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const _ = require('lodash');
|
||||
const Promise = require('bluebird');
|
||||
const Boom = require('boom');
|
||||
module.exports = (client) => {
|
||||
return (req, endpoint, params = {}) => {
|
||||
if (req.headers.authorization) {
|
||||
_.set(params, 'headers.authorization', req.headers.authorization);
|
||||
}
|
||||
const api = _.get(client, endpoint);
|
||||
if (!api) throw new Error(`callWithRequest called with an invalid endpoint: ${endpoint}`);
|
||||
return api.call(client, params)
|
||||
.catch((err) => {
|
||||
if (err.status === 401) {
|
||||
const options = { realm: 'Authorization Required' };
|
||||
return Promise.reject(Boom.unauthorized(err.body, 'Basic', options));
|
||||
}
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
};
|
|
@ -3,51 +3,70 @@ var _ = require('lodash');
|
|||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
var url = require('url');
|
||||
var callWithRequest = require('./call_with_request');
|
||||
|
||||
module.exports = function (server) {
|
||||
var config = server.config();
|
||||
var uri = url.parse(config.get('elasticsearch.url'));
|
||||
var username = config.get('elasticsearch.username');
|
||||
var password = config.get('elasticsearch.password');
|
||||
var verifySsl = config.get('elasticsearch.ssl.verify');
|
||||
var clientCrt = config.get('elasticsearch.ssl.cert');
|
||||
var clientKey = config.get('elasticsearch.ssl.key');
|
||||
var ca = config.get('elasticsearch.ssl.ca');
|
||||
var apiVersion = config.get('elasticsearch.apiVersion');
|
||||
|
||||
if (username && password) {
|
||||
uri.auth = util.format('%s:%s', username, password);
|
||||
}
|
||||
function createClient(options) {
|
||||
options = _.defaults(options || {}, {
|
||||
url: config.get('elasticsearch.url'),
|
||||
username: config.get('elasticsearch.username'),
|
||||
password: config.get('elasticsearch.password'),
|
||||
verifySsl: config.get('elasticsearch.ssl.verify'),
|
||||
clientCrt: config.get('elasticsearch.ssl.cert'),
|
||||
clientKey: config.get('elasticsearch.ssl.key'),
|
||||
ca: config.get('elasticsearch.ssl.ca'),
|
||||
apiVersion: config.get('elasticsearch.apiVersion'),
|
||||
keepAlive: true,
|
||||
auth: true
|
||||
});
|
||||
|
||||
var ssl = { rejectUnauthorized: verifySsl };
|
||||
if (clientCrt && clientKey) {
|
||||
ssl.cert = fs.readFileSync(clientCrt, 'utf8');
|
||||
ssl.key = fs.readFileSync(clientKey, 'utf8');
|
||||
}
|
||||
if (ca) {
|
||||
ssl.ca = fs.readFileSync(ca, 'utf8');
|
||||
}
|
||||
var uri = url.parse(options.url);
|
||||
|
||||
var client = new elasticsearch.Client({
|
||||
host: url.format(uri),
|
||||
ssl: ssl,
|
||||
apiVersion: apiVersion,
|
||||
log: function () {
|
||||
this.error = function (err) {
|
||||
server.log(['error', 'elasticsearch'], err);
|
||||
};
|
||||
this.warning = function (message) {
|
||||
server.log(['warning', 'elasticsearch'], message);
|
||||
};
|
||||
this.info = _.noop;
|
||||
this.debug = _.noop;
|
||||
this.trace = _.noop;
|
||||
this.close = _.noop;
|
||||
var authorization;
|
||||
if (options.auth && options.username && options.password) {
|
||||
uri.auth = util.format('%s:%s', options.username, options.password);
|
||||
}
|
||||
});
|
||||
|
||||
var ssl = { rejectUnauthorized: options.verifySsl };
|
||||
if (options.clientCrt && options.clientKey) {
|
||||
ssl.cert = fs.readFileSync(options.clientCrt, 'utf8');
|
||||
ssl.key = fs.readFileSync(options.clientKey, 'utf8');
|
||||
}
|
||||
if (options.ca) {
|
||||
ssl.ca = fs.readFileSync(options.ca, 'utf8');
|
||||
}
|
||||
|
||||
return new elasticsearch.Client({
|
||||
host: url.format(uri),
|
||||
ssl: ssl,
|
||||
apiVersion: options.apiVersion,
|
||||
keepAlive: options.keepAlive,
|
||||
log: function () {
|
||||
this.error = function (err) {
|
||||
server.log(['error', 'elasticsearch'], err);
|
||||
};
|
||||
this.warning = function (message) {
|
||||
server.log(['warning', 'elasticsearch'], message);
|
||||
};
|
||||
this.info = _.noop;
|
||||
this.debug = _.noop;
|
||||
this.trace = _.noop;
|
||||
this.close = _.noop;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var client = createClient();
|
||||
server.on('close', _.bindKey(client, 'close'));
|
||||
|
||||
var noAuthClient = createClient({ auth: false });
|
||||
server.on('close', _.bindKey(noAuthClient, 'close'));
|
||||
|
||||
server.expose('client', client);
|
||||
server.expose('createClient', createClient);
|
||||
server.expose('callWithRequest', callWithRequest(noAuthClient));
|
||||
|
||||
return client;
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@ module.exports = function mapUri(server, prefix) {
|
|||
return function (request, done) {
|
||||
var path = request.path.replace('/elasticsearch', '');
|
||||
var url = config.get('elasticsearch.url');
|
||||
if (!/\/$/.test(url)) url += '/';
|
||||
if (path) url = resolve(url, path);
|
||||
if (path) {
|
||||
if (/\/$/.test(url)) url = url.substring(0, url.length - 1);
|
||||
url += path;
|
||||
}
|
||||
var query = querystring.stringify(request.query);
|
||||
if (query) url += '?' + query;
|
||||
done(null, url);
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
ng-model="vis.params.yAxis.max"
|
||||
ng-required="vis.params.setYExtents">
|
||||
</label>
|
||||
<div ng-show="vis.params.yAxis.min > vis.params.yAxis.max">
|
||||
<span class="text-danger">Min must not exceed max</span>
|
||||
<div ng-show="vis.params.yAxis.min >= vis.params.yAxis.max">
|
||||
<span class="text-danger">Max must be greater than min</span>
|
||||
</div>
|
||||
<label>
|
||||
y-min
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = function (kibana) {
|
|||
app: {
|
||||
title: 'Kibana',
|
||||
description: 'the kibana you know and love',
|
||||
icon: 'plugins/kibana/settings/sections/about/barcode.svg',
|
||||
//icon: 'plugins/kibana/settings/sections/about/barcode.svg',
|
||||
main: 'plugins/kibana/kibana',
|
||||
uses: [
|
||||
'visTypes',
|
||||
|
|
|
@ -73,6 +73,16 @@
|
|||
<i aria-hidden="true" class="fa fa-plus-circle"></i>
|
||||
</button>
|
||||
</kbn-tooltip>
|
||||
<kbn-tooltip text="Options" placement="bottom" append-to-body="1">
|
||||
<button
|
||||
aria-label="Options"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="{{ configTemplate.is('options') }}"
|
||||
ng-class="{active: configTemplate.is('options')}"
|
||||
ng-click="configTemplate.toggle('options');">
|
||||
<i aria-hidden="true" class="fa fa-gear"></i>
|
||||
</button>
|
||||
</kbn-tooltip>
|
||||
</div>
|
||||
</navbar>
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
var angular = require('angular');
|
||||
var ConfigTemplate = require('ui/ConfigTemplate');
|
||||
var chrome = require('ui/chrome');
|
||||
|
||||
require('ui/directives/config');
|
||||
require('ui/courier');
|
||||
require('ui/config');
|
||||
require('ui/notify');
|
||||
require('ui/typeahead');
|
||||
require('ui/clipboard');
|
||||
|
||||
require('plugins/kibana/dashboard/directives/grid');
|
||||
require('plugins/kibana/dashboard/components/panel/panel');
|
||||
|
@ -32,7 +32,7 @@ define(function (require) {
|
|||
.when('/dashboard', {
|
||||
template: require('plugins/kibana/dashboard/index.html'),
|
||||
resolve: {
|
||||
dash: function (savedDashboards) {
|
||||
dash: function (savedDashboards, config) {
|
||||
return savedDashboards.get();
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ define(function (require) {
|
|||
|
||||
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl) {
|
||||
return {
|
||||
controller: function ($scope, $route, $routeParams, $location, Private, getAppState) {
|
||||
controller: function ($scope, $rootScope, $route, $routeParams, $location, Private, getAppState) {
|
||||
|
||||
var queryFilter = Private(require('ui/filter_bar/query_filter'));
|
||||
|
||||
|
@ -80,17 +80,23 @@ define(function (require) {
|
|||
var stateDefaults = {
|
||||
title: dash.title,
|
||||
panels: dash.panelsJSON ? JSON.parse(dash.panelsJSON) : [],
|
||||
options: dash.optionsJSON ? JSON.parse(dash.optionsJSON) : {},
|
||||
query: extractQueryFromFilters(dash.searchSource.getOwn('filter')) || {query_string: {query: '*'}},
|
||||
filters: _.reject(dash.searchSource.getOwn('filter'), matchQueryFilter)
|
||||
filters: _.reject(dash.searchSource.getOwn('filter'), matchQueryFilter),
|
||||
};
|
||||
|
||||
var $state = $scope.state = new AppState(stateDefaults);
|
||||
$scope.$watchCollection('state.options', function (newVal, oldVal) {
|
||||
if (!angular.equals(newVal, oldVal)) $state.save();
|
||||
});
|
||||
$scope.$watch('state.options.darkTheme', setDarkTheme);
|
||||
|
||||
$scope.configTemplate = new ConfigTemplate({
|
||||
save: require('plugins/kibana/dashboard/partials/save_dashboard.html'),
|
||||
load: require('plugins/kibana/dashboard/partials/load_dashboard.html'),
|
||||
share: require('plugins/kibana/dashboard/partials/share.html'),
|
||||
pickVis: require('plugins/kibana/dashboard/partials/pick_visualization.html')
|
||||
pickVis: require('plugins/kibana/dashboard/partials/pick_visualization.html'),
|
||||
options: require('plugins/kibana/dashboard/partials/options.html')
|
||||
});
|
||||
|
||||
$scope.refresh = _.bindKey(courier, 'fetch');
|
||||
|
@ -123,6 +129,12 @@ define(function (require) {
|
|||
}
|
||||
}
|
||||
|
||||
function setDarkTheme(enabled) {
|
||||
var theme = Boolean(enabled) ? 'theme-dark' : 'theme-light';
|
||||
chrome.removeApplicationClass(['theme-dark', 'theme-light']);
|
||||
chrome.addApplicationClass(theme);
|
||||
}
|
||||
|
||||
// update root source when filters update
|
||||
$scope.$listen(queryFilter, 'update', function () {
|
||||
updateQueryOnRootSource();
|
||||
|
@ -148,6 +160,7 @@ define(function (require) {
|
|||
dash.panelsJSON = angular.toJson($state.panels);
|
||||
dash.timeFrom = dash.timeRestore ? timefilter.time.from : undefined;
|
||||
dash.timeTo = dash.timeRestore ? timefilter.time.to : undefined;
|
||||
dash.optionsJSON = angular.toJson($state.options);
|
||||
|
||||
dash.save()
|
||||
.then(function (id) {
|
||||
|
@ -191,6 +204,7 @@ define(function (require) {
|
|||
// Setup configurable values for config directive, after objects are initialized
|
||||
$scope.opts = {
|
||||
dashboard: dash,
|
||||
ui: $state.options,
|
||||
save: $scope.save,
|
||||
addVis: $scope.addVis,
|
||||
addSearch: $scope.addSearch,
|
||||
|
|
10
src/plugins/kibana/public/dashboard/partials/options.html
Normal file
10
src/plugins/kibana/public/dashboard/partials/options.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<form role="form" class="options">
|
||||
<p>
|
||||
<div class="input-group">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="opts.ui.darkTheme" ng-checked="opts.ui.darkTheme">
|
||||
Use dark theme
|
||||
</label>
|
||||
</div>
|
||||
</p>
|
||||
</form>
|
|
@ -4,11 +4,11 @@
|
|||
<li ng-class="{active: mode == 'search'}"><a ng-click="mode='search'" title="Searches">Searches</a></li>
|
||||
</ul>
|
||||
|
||||
<li class="list-group-item" ng-switch-when="visualization">
|
||||
<div class="list-group-item" ng-switch-when="visualization">
|
||||
<saved-object-finder title="Choose a visualization" type="visualizations" on-choose="opts.addVis"></saved-object-finder>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<li class="list-group-item" ng-switch-when="search">
|
||||
<div class="list-group-item" ng-switch-when="search">
|
||||
<saved-object-finder title="Choose a search" type="searches" on-choose="opts.addSearch"></saved-object-finder>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
<p>
|
||||
<div class="input-group">
|
||||
<label>
|
||||
Embed this dashboard.
|
||||
<kbn-clipboard copy="opts.shareData().embed"></kbn-clipboard>
|
||||
Embed this dashboard
|
||||
<small>Add to your html source. Note all clients must still be able to access kibana</small>
|
||||
</label>
|
||||
<div class="form-control" disabled>{{opts.shareData().embed}}</div>
|
||||
|
@ -15,7 +14,6 @@
|
|||
<div class="input-group">
|
||||
<label>
|
||||
Share a link
|
||||
<kbn-clipboard copy="opts.shareData().link"></kbn-clipboard>
|
||||
</label>
|
||||
<div class="form-control" disabled>{{opts.shareData().link}}</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
define(function (require) {
|
||||
var module = require('ui/modules').get('app/dashboard');
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
|
||||
// Used only by the savedDashboards service, usually no reason to change this
|
||||
module.factory('SavedDashboard', function (courier) {
|
||||
|
||||
module.factory('SavedDashboard', function (courier, config) {
|
||||
// SavedDashboard constructor. Usually you'd interact with an instance of this.
|
||||
// ID is option, without it one will be generated on save.
|
||||
_.class(SavedDashboard).inherits(courier.SavedObject);
|
||||
|
@ -25,10 +25,13 @@ define(function (require) {
|
|||
hits: 0,
|
||||
description: '',
|
||||
panelsJSON: '[]',
|
||||
optionsJSON: angular.toJson({
|
||||
darkTheme: config.get('dashboard:defaultDarkTheme')
|
||||
}),
|
||||
version: 1,
|
||||
timeRestore: false,
|
||||
timeTo: undefined,
|
||||
timeFrom: undefined
|
||||
timeFrom: undefined,
|
||||
},
|
||||
|
||||
// if an indexPattern was saved with the searchsource of a SavedDashboard
|
||||
|
@ -46,10 +49,11 @@ define(function (require) {
|
|||
hits: 'integer',
|
||||
description: 'string',
|
||||
panelsJSON: 'string',
|
||||
optionsJSON: 'string',
|
||||
version: 'integer',
|
||||
timeRestore: 'boolean',
|
||||
timeTo: 'string',
|
||||
timeFrom: 'string'
|
||||
timeFrom: 'string',
|
||||
};
|
||||
|
||||
SavedDashboard.searchsource = true;
|
||||
|
|
|
@ -43,7 +43,7 @@ define(function (require) {
|
|||
};
|
||||
|
||||
|
||||
this.find = function (searchString) {
|
||||
this.find = function (searchString, size = 100) {
|
||||
var self = this;
|
||||
var body;
|
||||
if (searchString) {
|
||||
|
@ -64,7 +64,7 @@ define(function (require) {
|
|||
index: kbnIndex,
|
||||
type: 'dashboard',
|
||||
body: body,
|
||||
size: 100
|
||||
size: size
|
||||
})
|
||||
.then(function (resp) {
|
||||
return {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
@import (reference) "~ui/styles/variables";
|
||||
@import (reference) "~ui/styles/mixins";
|
||||
@dashboard-background: @gray-lighter;
|
||||
|
||||
.tab-dashboard {
|
||||
background-color: @dashboard-background;
|
||||
background-color: @dashboard-bg;
|
||||
}
|
||||
|
||||
dashboard-grid {
|
||||
|
@ -14,14 +13,14 @@ dashboard-grid {
|
|||
|
||||
.start-screen {
|
||||
margin: 20px;
|
||||
background-color: @gray-lighter;
|
||||
background-color: @dashboard-bg;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.gridster {
|
||||
list-style-type: none;
|
||||
display: block;
|
||||
background-color: @dashboard-background;
|
||||
background-color: @dashboard-bg;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
|
@ -41,8 +40,8 @@ dashboard-grid {
|
|||
dashboard-panel {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: @panel-bg;
|
||||
color: @text-color;
|
||||
background: @dashboard-panel-bg;
|
||||
color: @dashboard-panel-color;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
@ -61,6 +60,8 @@ dashboard-grid {
|
|||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
|
||||
div.btn-group {
|
||||
white-space: nowrap;
|
||||
|
@ -85,12 +86,12 @@ dashboard-grid {
|
|||
}
|
||||
|
||||
a {
|
||||
color: @text-color;
|
||||
color: @dashboard-panel-heading-link-color;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0px 3px;
|
||||
&:hover {
|
||||
color: @link-hover-color;
|
||||
color: @dashboard-panel-heading-link-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ dashboard-grid {
|
|||
|
||||
.fa-exclamation-triangle {
|
||||
font-size: 2em;
|
||||
color: @btn-danger-bg;
|
||||
color: @dashboard-panel-load-error-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
this.find = function (searchString) {
|
||||
this.find = function (searchString, size = 100) {
|
||||
var self = this;
|
||||
var body;
|
||||
if (searchString) {
|
||||
|
@ -67,7 +67,7 @@ define(function (require) {
|
|||
index: kbnIndex,
|
||||
type: 'search',
|
||||
body: body,
|
||||
size: 100
|
||||
size: size
|
||||
})
|
||||
.then(function (resp) {
|
||||
return {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
&-wrapper {
|
||||
padding-right: 0px !important;
|
||||
padding-left: @collapser-width;
|
||||
}
|
||||
|
||||
&-content {
|
||||
|
@ -61,16 +62,16 @@
|
|||
|
||||
opacity: 0.75;
|
||||
text-align: center;
|
||||
background-color: #ffffff;
|
||||
background-color: transparent;
|
||||
|
||||
// spinner and prorgess counter
|
||||
// spinner and progress counter
|
||||
> div {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
background-color: @well-bg;
|
||||
background-color: @discover-info-bg;
|
||||
float: right;
|
||||
padding: 5px 10px;
|
||||
border-bottom-left-radius: @border-radius-base;
|
||||
|
@ -94,7 +95,7 @@
|
|||
}
|
||||
|
||||
&-footer {
|
||||
background-color: @well-bg;
|
||||
background-color: @discover-table-footer-bg;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
|
@ -116,7 +117,7 @@
|
|||
|
||||
&-field {
|
||||
&-filter {
|
||||
background-color: @well-bg;
|
||||
background-color: @discover-field-filter-bg;
|
||||
margin-right: 10px;
|
||||
|
||||
.form-group {
|
||||
|
@ -126,16 +127,17 @@
|
|||
}
|
||||
|
||||
&-toggle {
|
||||
color: @btn-success-color;
|
||||
color: @discover-field-toggle-color;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shard-failures {
|
||||
color: @brand-danger;
|
||||
background-color: lighten(@brand-danger, 40%) !important;
|
||||
border: 1px solid @brand-danger;
|
||||
color: @discover-shard-failures-color;
|
||||
background-color: @discover-shard-failures-bg !important;
|
||||
border: 1px solid;
|
||||
border-color: @discover-shard-failures-border;
|
||||
border-radius: @border-radius-base;
|
||||
padding: 0 20px 20px;
|
||||
li {
|
||||
|
@ -157,19 +159,21 @@
|
|||
}
|
||||
|
||||
.discover-field-details {
|
||||
border-top: 1px solid @well-border;
|
||||
border-top: 1px solid;
|
||||
border-top-color: @discover-field-details-border;
|
||||
padding: 5px 10px;
|
||||
background-color: @body-bg;
|
||||
color: @text-color;
|
||||
background-color: @discover-field-details-bg;
|
||||
color: @discover-field-details-color;
|
||||
|
||||
&-close {
|
||||
text-align: center;
|
||||
border-top: 1px solid @well-border;
|
||||
background-color: @well-bg;
|
||||
border-top: 1px solid;
|
||||
border-color: @discover-field-details-close-border;
|
||||
background-color: @discover-field-details-close-bg;
|
||||
|
||||
&:hover {
|
||||
background-color: @sidebar-hover-bg;
|
||||
color: @sidebar-hover-color;
|
||||
background-color: @discover-field-details-close-hover-bg;
|
||||
color: @discover-field-details-close-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,11 +194,11 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: @link-color !important;
|
||||
color: @discover-link-color !important;
|
||||
}
|
||||
|
||||
a.btn {
|
||||
color: @text-color !important;
|
||||
color: @discover-link-btn-color !important;
|
||||
}
|
||||
|
||||
.progress {
|
||||
|
@ -208,7 +212,7 @@
|
|||
border-radius: 4px;
|
||||
|
||||
span {
|
||||
background-color: @gray;
|
||||
background-color: @discover-field-details-progress-bar-bg;
|
||||
padding: 3px;
|
||||
border-radius: 4px;
|
||||
margin-left: 3px;
|
||||
|
@ -225,11 +229,11 @@ field-name,
|
|||
text-align: center;
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
color: @text-muted;
|
||||
color: @field-name-color;
|
||||
}
|
||||
|
||||
&.no-results {
|
||||
color: @text-muted;
|
||||
color: @field-name-no-results-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ chrome
|
|||
.setNavBackground('#222222')
|
||||
.setTabDefaults({
|
||||
resetWhenActive: true,
|
||||
trackLastPath: true,
|
||||
lastUrlStore: window.sessionStore,
|
||||
activeIndicatorColor: '#656a76'
|
||||
})
|
||||
.setTabs([
|
||||
|
@ -47,10 +47,9 @@ chrome
|
|||
title: 'Settings'
|
||||
}
|
||||
])
|
||||
.setRootController('kibana', function ($scope, courier) {
|
||||
.setRootController('kibana', function ($scope, $rootScope, courier, config) {
|
||||
// wait for the application to finish loading
|
||||
$scope.$on('application.load', function () {
|
||||
courier.start();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
height="64"
|
||||
id="svg5235"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel+osxmenu r12835"
|
||||
inkscape:version="0.91 r13725"
|
||||
viewBox="0 0 64 64"
|
||||
sodipodi:docname="barcode.svg">
|
||||
<defs
|
||||
|
@ -45,7 +45,7 @@
|
|||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
|
@ -54,404 +54,401 @@
|
|||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g6209"
|
||||
transform="matrix(0.57440105,0,0,0.57440105,-36.987859,-56.970747)">
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="97.825981"
|
||||
height="100.54836"
|
||||
width="2.4159346"
|
||||
id="rect5251-3"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="85.735123"
|
||||
height="100.54836"
|
||||
width="7.5297971"
|
||||
id="rect5251-9"
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="75.889793"
|
||||
height="100.54836"
|
||||
width="5.0022545"
|
||||
id="rect5251-8"
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="72.783882"
|
||||
height="100.54836"
|
||||
width="3.2287681"
|
||||
id="rect5251-2"
|
||||
style="opacity:1;fill:#2d448e;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="69.63768"
|
||||
height="100.54836"
|
||||
width="3.159905"
|
||||
id="rect5251"
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="119.37186"
|
||||
height="100.54836"
|
||||
width="6.9419966"
|
||||
id="rect5251-9-9"
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="131.24544"
|
||||
height="100.54836"
|
||||
width="4.9434743"
|
||||
id="rect5251-5-9"
|
||||
style="opacity:1;fill:#006555;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="83.986267"
|
||||
height="100.54836"
|
||||
width="1.8869137"
|
||||
id="rect5251-5-0"
|
||||
style="opacity:1;fill:#006555;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="93.214729"
|
||||
height="100.54836"
|
||||
width="4.7083545"
|
||||
id="rect5251-7"
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="136.24173"
|
||||
height="100.54836"
|
||||
width="5.8251753"
|
||||
id="rect5251-36"
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="80.870918"
|
||||
height="100.54836"
|
||||
width="3.1212945"
|
||||
id="rect5251-3-7"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="100.15076"
|
||||
height="100.54836"
|
||||
width="3.5915353"
|
||||
id="rect5251-9-9-3"
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="166.68982"
|
||||
height="100.54836"
|
||||
width="3.9442151"
|
||||
id="rect5251-9-9-8"
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="110.55486"
|
||||
height="100.54836"
|
||||
width="3.3564136"
|
||||
id="rect5251-3-3"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33429"
|
||||
x="148.82066"
|
||||
height="100.54836"
|
||||
width="4.1793332"
|
||||
id="rect5251-3-0"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33427"
|
||||
x="129.07057"
|
||||
height="100.54838"
|
||||
width="2.1808138"
|
||||
id="rect5251-8-3"
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="142.00218"
|
||||
height="100.54836"
|
||||
width="3.0037344"
|
||||
id="rect5251-8-33"
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="106.20512"
|
||||
height="100.54836"
|
||||
width="4.3556747"
|
||||
id="rect5251-7-3"
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="103.73636"
|
||||
height="100.54836"
|
||||
width="2.4159336"
|
||||
id="rect5251-2-0"
|
||||
style="opacity:1;fill:#2d448e;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="116.25652"
|
||||
height="100.54836"
|
||||
width="3.8266549"
|
||||
id="rect5251-36-7"
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33427"
|
||||
x="113.9641"
|
||||
height="100.54838"
|
||||
width="2.2983737"
|
||||
id="rect5251-5-3"
|
||||
style="opacity:1;fill:#006555;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="126.30791"
|
||||
height="100.54836"
|
||||
width="2.8273947"
|
||||
id="rect5251-3-7-7"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="144.8824"
|
||||
height="100.54836"
|
||||
width="2.0632553"
|
||||
id="rect5251-3-7-70"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="146.99847"
|
||||
height="100.54836"
|
||||
width="1.8869162"
|
||||
id="rect5251-9-9-8-4"
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="158.32114"
|
||||
height="100.54836"
|
||||
width="4.708354"
|
||||
id="rect5251-3-7-70-2"
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33427"
|
||||
x="152.97215"
|
||||
height="100.54838"
|
||||
width="5.413713"
|
||||
id="rect5251-8-339-2"
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="104.33428"
|
||||
x="162.99417"
|
||||
height="100.54836"
|
||||
width="3.8266544"
|
||||
id="rect5251-36-5"
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
id="g4300"
|
||||
transform="matrix(1.1002186,0,0,1.1002186,-3.2119331,-3.0092752)">
|
||||
<g
|
||||
transform="matrix(0.57440105,0,0,0.57440105,-36.987859,-56.970747)"
|
||||
id="g6209">
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3"
|
||||
width="2.4159346"
|
||||
height="100.54836"
|
||||
x="97.825981"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-9"
|
||||
width="7.5297971"
|
||||
height="100.54836"
|
||||
x="85.735123"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-8"
|
||||
width="5.0022545"
|
||||
height="100.54836"
|
||||
x="75.889793"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#2d448e;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-2"
|
||||
width="3.2287681"
|
||||
height="100.54836"
|
||||
x="72.783882"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251"
|
||||
width="3.159905"
|
||||
height="100.54836"
|
||||
x="69.63768"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-9-9"
|
||||
width="6.9419966"
|
||||
height="100.54836"
|
||||
x="119.37186"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#006555;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-5-9"
|
||||
width="4.9434743"
|
||||
height="100.54836"
|
||||
x="131.24544"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#006555;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-5-0"
|
||||
width="1.8869137"
|
||||
height="100.54836"
|
||||
x="83.986267"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-7"
|
||||
width="4.7083545"
|
||||
height="100.54836"
|
||||
x="93.214729"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-36"
|
||||
width="5.8251753"
|
||||
height="100.54836"
|
||||
x="136.24173"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3-7"
|
||||
width="3.1212945"
|
||||
height="100.54836"
|
||||
x="80.870918"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-9-9-3"
|
||||
width="3.5915353"
|
||||
height="100.54836"
|
||||
x="100.15076"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-9-9-8"
|
||||
width="3.9442151"
|
||||
height="100.54836"
|
||||
x="166.68982"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3-3"
|
||||
width="3.3564136"
|
||||
height="100.54836"
|
||||
x="110.55486"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3-0"
|
||||
width="4.1793332"
|
||||
height="100.54836"
|
||||
x="148.82066"
|
||||
y="104.33429" />
|
||||
<rect
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-8-3"
|
||||
width="2.1808138"
|
||||
height="100.54838"
|
||||
x="129.07057"
|
||||
y="104.33427" />
|
||||
<rect
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-8-33"
|
||||
width="3.0037344"
|
||||
height="100.54836"
|
||||
x="142.00218"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-7-3"
|
||||
width="4.3556747"
|
||||
height="100.54836"
|
||||
x="106.20512"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#2d448e;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-2-0"
|
||||
width="2.4159336"
|
||||
height="100.54836"
|
||||
x="103.73636"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-36-7"
|
||||
width="3.8266549"
|
||||
height="100.54836"
|
||||
x="116.25652"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#006555;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-5-3"
|
||||
width="2.2983737"
|
||||
height="100.54838"
|
||||
x="113.9641"
|
||||
y="104.33427" />
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3-7-7"
|
||||
width="2.8273947"
|
||||
height="100.54836"
|
||||
x="126.30791"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3-7-70"
|
||||
width="2.0632553"
|
||||
height="100.54836"
|
||||
x="144.8824"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#ea458b;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-9-9-8-4"
|
||||
width="1.8869162"
|
||||
height="100.54836"
|
||||
x="146.99847"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#39bdb1;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-3-7-70-2"
|
||||
width="4.708354"
|
||||
height="100.54836"
|
||||
x="158.32114"
|
||||
y="104.33428" />
|
||||
<rect
|
||||
style="opacity:1;fill:#f3bb19;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-8-339-2"
|
||||
width="5.413713"
|
||||
height="100.54838"
|
||||
x="152.97215"
|
||||
y="104.33427" />
|
||||
<rect
|
||||
style="opacity:1;fill:#83cb1c;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5251-36-5"
|
||||
width="3.8266544"
|
||||
height="100.54836"
|
||||
x="162.99417"
|
||||
y="104.33428" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.57440105,0,0,1.9251702,3.0622581,2.9589583)"
|
||||
id="g4544">
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="1"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="30"
|
||||
id="line4450" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="3.5"
|
||||
y1="0"
|
||||
x2="3.5"
|
||||
y2="30"
|
||||
id="line4452" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="6.5"
|
||||
y1="0"
|
||||
x2="6.5"
|
||||
y2="30"
|
||||
id="line4454" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="11.5"
|
||||
y1="0"
|
||||
x2="11.5"
|
||||
y2="30"
|
||||
id="line4456" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="14.5"
|
||||
y1="0"
|
||||
x2="14.5"
|
||||
y2="30"
|
||||
id="line4458" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="17"
|
||||
y1="0"
|
||||
x2="17"
|
||||
y2="30"
|
||||
id="line4460" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="23"
|
||||
y1="0"
|
||||
x2="23"
|
||||
y2="30"
|
||||
id="line4462" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="28.5"
|
||||
y1="0"
|
||||
x2="28.5"
|
||||
y2="30"
|
||||
id="line4464" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="30.5"
|
||||
y1="0"
|
||||
x2="30.5"
|
||||
y2="30"
|
||||
id="line4466" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="34"
|
||||
y1="0"
|
||||
x2="34"
|
||||
y2="30"
|
||||
id="line4468" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="37"
|
||||
y1="0"
|
||||
x2="37"
|
||||
y2="30"
|
||||
id="line4470" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="41"
|
||||
y1="0"
|
||||
x2="41"
|
||||
y2="30"
|
||||
id="line4472" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="44.5"
|
||||
y1="0"
|
||||
x2="44.5"
|
||||
y2="30"
|
||||
id="line4474" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="47"
|
||||
y1="0"
|
||||
x2="47"
|
||||
y2="30"
|
||||
id="line4476" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="50.5"
|
||||
y1="0"
|
||||
x2="50.5"
|
||||
y2="30"
|
||||
id="line4478" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="56"
|
||||
y1="0"
|
||||
x2="56"
|
||||
y2="30"
|
||||
id="line4480" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="59.5"
|
||||
y1="0"
|
||||
x2="59.5"
|
||||
y2="30"
|
||||
id="line4482" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="61.5"
|
||||
y1="0"
|
||||
x2="61.5"
|
||||
y2="30"
|
||||
id="line4484" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="67"
|
||||
y1="0"
|
||||
x2="67"
|
||||
y2="30"
|
||||
id="line4486" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="72.5"
|
||||
y1="0"
|
||||
x2="72.5"
|
||||
y2="30"
|
||||
id="line4488" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="75.5"
|
||||
y1="0"
|
||||
x2="75.5"
|
||||
y2="30"
|
||||
id="line4490" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="77.5"
|
||||
y1="0"
|
||||
x2="77.5"
|
||||
y2="30"
|
||||
id="line4492" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="79.5"
|
||||
y1="0"
|
||||
x2="79.5"
|
||||
y2="30"
|
||||
id="line4494" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:4"
|
||||
x1="83"
|
||||
y1="0"
|
||||
x2="83"
|
||||
y2="30"
|
||||
id="line4496" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="89"
|
||||
y1="0"
|
||||
x2="89"
|
||||
y2="30"
|
||||
id="line4498" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:3"
|
||||
x1="94.5"
|
||||
y1="0"
|
||||
x2="94.5"
|
||||
y2="30"
|
||||
id="line4500" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:1"
|
||||
x1="97.5"
|
||||
y1="0"
|
||||
x2="97.5"
|
||||
y2="30"
|
||||
id="line4502" />
|
||||
<line
|
||||
style="fill:none;stroke:#000000;stroke-width:2"
|
||||
x1="100"
|
||||
y1="0"
|
||||
x2="100"
|
||||
y2="30"
|
||||
id="line4504" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4544"
|
||||
transform="matrix(0.57440105,0,0,1.9251702,3.0622581,2.9589583)">
|
||||
<line
|
||||
id="line4450"
|
||||
y2="30"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="1"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4452"
|
||||
y2="30"
|
||||
x2="3.5"
|
||||
y1="0"
|
||||
x1="3.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4454"
|
||||
y2="30"
|
||||
x2="6.5"
|
||||
y1="0"
|
||||
x1="6.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4456"
|
||||
y2="30"
|
||||
x2="11.5"
|
||||
y1="0"
|
||||
x1="11.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4458"
|
||||
y2="30"
|
||||
x2="14.5"
|
||||
y1="0"
|
||||
x1="14.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4460"
|
||||
y2="30"
|
||||
x2="17"
|
||||
y1="0"
|
||||
x1="17"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4462"
|
||||
y2="30"
|
||||
x2="23"
|
||||
y1="0"
|
||||
x1="23"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4464"
|
||||
y2="30"
|
||||
x2="28.5"
|
||||
y1="0"
|
||||
x1="28.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4466"
|
||||
y2="30"
|
||||
x2="30.5"
|
||||
y1="0"
|
||||
x1="30.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4468"
|
||||
y2="30"
|
||||
x2="34"
|
||||
y1="0"
|
||||
x1="34"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4470"
|
||||
y2="30"
|
||||
x2="37"
|
||||
y1="0"
|
||||
x1="37"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4472"
|
||||
y2="30"
|
||||
x2="41"
|
||||
y1="0"
|
||||
x1="41"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4474"
|
||||
y2="30"
|
||||
x2="44.5"
|
||||
y1="0"
|
||||
x1="44.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4476"
|
||||
y2="30"
|
||||
x2="47"
|
||||
y1="0"
|
||||
x1="47"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4478"
|
||||
y2="30"
|
||||
x2="50.5"
|
||||
y1="0"
|
||||
x1="50.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4480"
|
||||
y2="30"
|
||||
x2="56"
|
||||
y1="0"
|
||||
x1="56"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4482"
|
||||
y2="30"
|
||||
x2="59.5"
|
||||
y1="0"
|
||||
x1="59.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4484"
|
||||
y2="30"
|
||||
x2="61.5"
|
||||
y1="0"
|
||||
x1="61.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4486"
|
||||
y2="30"
|
||||
x2="67"
|
||||
y1="0"
|
||||
x1="67"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4488"
|
||||
y2="30"
|
||||
x2="72.5"
|
||||
y1="0"
|
||||
x1="72.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4490"
|
||||
y2="30"
|
||||
x2="75.5"
|
||||
y1="0"
|
||||
x1="75.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4492"
|
||||
y2="30"
|
||||
x2="77.5"
|
||||
y1="0"
|
||||
x1="77.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4494"
|
||||
y2="30"
|
||||
x2="79.5"
|
||||
y1="0"
|
||||
x1="79.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4496"
|
||||
y2="30"
|
||||
x2="83"
|
||||
y1="0"
|
||||
x1="83"
|
||||
style="fill:none;stroke:#000000;stroke-width:4" />
|
||||
<line
|
||||
id="line4498"
|
||||
y2="30"
|
||||
x2="89"
|
||||
y1="0"
|
||||
x1="89"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
<line
|
||||
id="line4500"
|
||||
y2="30"
|
||||
x2="94.5"
|
||||
y1="0"
|
||||
x1="94.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:3" />
|
||||
<line
|
||||
id="line4502"
|
||||
y2="30"
|
||||
x2="97.5"
|
||||
y1="0"
|
||||
x1="97.5"
|
||||
style="fill:none;stroke:#000000;stroke-width:1" />
|
||||
<line
|
||||
id="line4504"
|
||||
y2="30"
|
||||
x2="100"
|
||||
y1="0"
|
||||
x1="100"
|
||||
style="fill:none;stroke:#000000;stroke-width:2" />
|
||||
</g>
|
||||
<rect
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:3.01796293;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6306"
|
||||
width="61.028111"
|
||||
height="60.597305"
|
||||
x="1.5768572"
|
||||
y="1.5882187" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
@ -1,9 +1,12 @@
|
|||
<tr ng-class="conf.value === undefined ? 'default' : 'custom'">
|
||||
<td class="name">
|
||||
<b>{{conf.name}}</b>
|
||||
<span class="smaller" ng-show="conf.value !== undefined">
|
||||
<span class="smaller" ng-show="!conf.isCustom && conf.value !== undefined">
|
||||
(Default: <i>{{conf.defVal == undefined ? 'null' : conf.defVal}}</i>)
|
||||
</span>
|
||||
<span class="smaller" ng-show="conf.isCustom">
|
||||
(Custom setting)
|
||||
</span>
|
||||
<br>
|
||||
<span class="smaller" ng-bind-html="conf.description | trustAsHtml"></span>
|
||||
</td>
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
<kbn-settings-advanced class="container">
|
||||
<div class="bs-callout bs-callout-warning">
|
||||
<h4>Caution: You can break stuff here</h4>
|
||||
Be careful in here, these settings are for very advanced users only. Tweaks you make here can break large portions of Kibana.
|
||||
Some of these settings may be undocumented, unsupported or experimental. Blanking a field will cause Kibana to use its internal
|
||||
defaults which may be unacceptable given other configuration directives.
|
||||
Be careful in here, these settings are for very advanced users only.
|
||||
Tweaks you make here can break large portionsof Kibana. Some of these
|
||||
settings may be undocumented, unsupported or experimental. If a field has
|
||||
a default value, blanking the field will reset it to its default which
|
||||
may be unacceptable given other configuration directives. Deleting a
|
||||
custom setting will permanently remove it from Kibana's config.
|
||||
</div>
|
||||
<form role="form">
|
||||
<input aria-label="Filter" ng-model="advancedFilter" class="form-control span12" type="text" placeholder="Filter"/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var getValType = require('plugins/kibana/settings/sections/advanced/lib/get_val_type');
|
||||
var toEditableConfig = require('plugins/kibana/settings/sections/advanced/lib/to_editable_config');
|
||||
|
||||
|
||||
require('plugins/kibana/settings/sections/advanced/advanced_row');
|
||||
|
@ -20,46 +20,34 @@ define(function (require) {
|
|||
ESC: 27
|
||||
};
|
||||
|
||||
var NAMED_EDITORS = ['json', 'array', 'boolean', 'select'];
|
||||
var NORMAL_EDITOR = ['number', 'string', 'null', 'undefined'];
|
||||
|
||||
function getEditorType(conf) {
|
||||
if (_.contains(NAMED_EDITORS, conf.type)) return conf.type;
|
||||
if (_.contains(NORMAL_EDITOR, conf.type)) return 'normal';
|
||||
}
|
||||
|
||||
function isTypeComplex(conf) {
|
||||
return !(conf.json || conf.array || conf.bool || conf.normal);
|
||||
}
|
||||
|
||||
function notDefaultConfig(configName) {
|
||||
return !(configName in configDefaults);
|
||||
}
|
||||
|
||||
function readConfigVals() {
|
||||
var configVals = config._vals();
|
||||
|
||||
$scope.configs = _.map(configDefaults, function (def, name) {
|
||||
var val = configVals[name];
|
||||
var conf = {
|
||||
name: name,
|
||||
defVal: def.value,
|
||||
type: getValType(def, val),
|
||||
description: def.description,
|
||||
options: def.options,
|
||||
value: val,
|
||||
};
|
||||
var customConfig = Object.keys(configVals)
|
||||
.filter(notDefaultConfig)
|
||||
.map(name => toEditableConfig(false, name, configVals[name]));
|
||||
|
||||
var editor = getEditorType(conf);
|
||||
conf.json = editor === 'json';
|
||||
conf.select = editor === 'select';
|
||||
conf.bool = editor === 'boolean';
|
||||
conf.array = editor === 'array';
|
||||
conf.normal = editor === 'normal';
|
||||
conf.tooComplex = !editor;
|
||||
|
||||
return conf;
|
||||
});
|
||||
$scope.configs = _(configDefaults)
|
||||
.map((def, name) => toEditableConfig(def, name, configVals[name]))
|
||||
.reject('readonly')
|
||||
.concat(customConfig)
|
||||
.value();
|
||||
}
|
||||
|
||||
// react to changes of the config values
|
||||
var unhook = $rootScope.$on('change:config', readConfigVals);
|
||||
$scope.$on('$destroy', unhook);
|
||||
|
||||
// initial config setup
|
||||
readConfigVals();
|
||||
$rootScope.$on('change:config', readConfigVals);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
var getEditorType = require('plugins/kibana/settings/sections/advanced/lib/get_editor_type');
|
||||
var expect = require('expect.js');
|
||||
|
||||
describe('Settings', function () {
|
||||
describe('Advanced', function () {
|
||||
describe('getEditorType(conf)', function () {
|
||||
context('when given type has a named editor', function () {
|
||||
it('returns that named editor', function () {
|
||||
expect(getEditorType({ type: 'json' })).to.equal('json');
|
||||
expect(getEditorType({ type: 'array' })).to.equal('array');
|
||||
expect(getEditorType({ type: 'boolean' })).to.equal('boolean');
|
||||
expect(getEditorType({ type: 'select' })).to.equal('select');
|
||||
});
|
||||
});
|
||||
|
||||
context('when given a type of number, string, null, or undefined', function () {
|
||||
it('returns "normal"', function () {
|
||||
expect(getEditorType({ type: 'number' })).to.equal('normal');
|
||||
expect(getEditorType({ type: 'string' })).to.equal('normal');
|
||||
expect(getEditorType({ type: 'null' })).to.equal('normal');
|
||||
expect(getEditorType({ type: 'undefined' })).to.equal('normal');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
|
||||
var toEditableConfig = require('plugins/kibana/settings/sections/advanced/lib/to_editable_config');
|
||||
var expect = require('expect.js');
|
||||
|
||||
describe('Settings', function () {
|
||||
describe('Advanced', function () {
|
||||
describe('toEditableConfig(def, name, value)', function () {
|
||||
it('sets name', function () {
|
||||
expect(invoke({ name: 'who' }).name).to.equal('who');
|
||||
});
|
||||
|
||||
it('sets value', function () {
|
||||
expect(invoke({ value: 'what' }).value).to.equal('what');
|
||||
});
|
||||
|
||||
it('sets type', function () {
|
||||
expect(invoke({ value: 'what' }).type).to.be('string');
|
||||
expect(invoke({ value: 0 }).type).to.be('number');
|
||||
expect(invoke({ value: [] }).type).to.be('array');
|
||||
});
|
||||
|
||||
context('when given a setting definition object', function () {
|
||||
var def;
|
||||
beforeEach(function () {
|
||||
def = {
|
||||
value: 'the original',
|
||||
description: 'the one and only',
|
||||
options: 'all the options'
|
||||
};
|
||||
});
|
||||
|
||||
it('is not marked as custom', function () {
|
||||
expect(invoke({ def }).isCustom).to.be.false;
|
||||
});
|
||||
|
||||
it('sets a default value', function () {
|
||||
expect(invoke({ def }).defVal).to.equal(def.value);
|
||||
});
|
||||
|
||||
it('sets a description', function () {
|
||||
expect(invoke({ def }).description).to.equal(def.description);
|
||||
});
|
||||
|
||||
it('sets options', function () {
|
||||
expect(invoke({ def }).options).to.equal(def.options);
|
||||
});
|
||||
|
||||
context('that contains a type', function () {
|
||||
it('sets that type', function () {
|
||||
def.type = 'something';
|
||||
expect(invoke({ def }).type).to.equal(def.type);
|
||||
});
|
||||
});
|
||||
|
||||
context('that contains a value of type array', function () {
|
||||
it('sets type to array', function () {
|
||||
def.value = [];
|
||||
expect(invoke({ def }).type).to.equal('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when not given a setting definition object', function () {
|
||||
it('is marked as custom', function () {
|
||||
expect(invoke().isCustom).to.be.true;
|
||||
});
|
||||
|
||||
it('sets defVal to undefined', function () {
|
||||
expect(invoke().defVal).to.be.undefined;
|
||||
});
|
||||
|
||||
it('sets description to undefined', function () {
|
||||
expect(invoke().description).to.be.undefined;
|
||||
});
|
||||
|
||||
it('sets options to undefined', function () {
|
||||
expect(invoke().options).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function invoke({ def = false, name = 'woah', value = 'forreal' } = {}) {
|
||||
return toEditableConfig(def, name, value);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var NAMED_EDITORS = ['json', 'array', 'boolean', 'select'];
|
||||
var NORMAL_EDITOR = ['number', 'string', 'null', 'undefined'];
|
||||
|
||||
/**
|
||||
* @param {object} advanced setting configuration object
|
||||
* @returns {string} the editor type to use when editing value
|
||||
*/
|
||||
function getEditorType(conf) {
|
||||
if (_.contains(NAMED_EDITORS, conf.type)) return conf.type;
|
||||
if (_.contains(NORMAL_EDITOR, conf.type)) return 'normal';
|
||||
}
|
||||
|
||||
return getEditorType;
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var getValType = require('./get_val_type');
|
||||
var getEditorType = require('./get_editor_type');
|
||||
|
||||
/**
|
||||
* @param {object} advanced setting definition object
|
||||
* @param {object} name of setting
|
||||
* @param {object} current value of setting
|
||||
* @returns {object} the editable config object
|
||||
*/
|
||||
function toEditableConfig(def, name, value) {
|
||||
var isCustom = !def;
|
||||
if (isCustom) def = {};
|
||||
|
||||
var conf = {
|
||||
name,
|
||||
value,
|
||||
isCustom,
|
||||
readonly: !!def.readonly,
|
||||
defVal: def.value,
|
||||
type: getValType(def, value),
|
||||
description: def.description,
|
||||
options: def.options
|
||||
};
|
||||
|
||||
var editor = getEditorType(conf);
|
||||
conf.json = editor === 'json';
|
||||
conf.select = editor === 'select';
|
||||
conf.bool = editor === 'boolean';
|
||||
conf.array = editor === 'array';
|
||||
conf.normal = editor === 'normal';
|
||||
conf.tooComplex = !editor;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
return toEditableConfig;
|
||||
});
|
|
@ -2,7 +2,7 @@
|
|||
<kbn-settings-objects class="container">
|
||||
<div class="header">
|
||||
<h2 class="title">Edit Saved Objects</h2>
|
||||
<button class="btn btn-default controls" ng-click="exportAll()"><i aria-hidden="true" class="fa fa-download"></i> Export</button>
|
||||
<button class="btn btn-default controls" ng-click="exportAll()"><i aria-hidden="true" class="fa fa-download"></i> Export Everything</button>
|
||||
<button file-upload="importAll(fileContents)" class="btn btn-default controls" ng-click><i aria-hidden="true" class="fa fa-upload"></i> Import</button>
|
||||
</div>
|
||||
<p>
|
||||
|
|
|
@ -4,6 +4,7 @@ define(function (require) {
|
|||
var saveAs = require('@spalger/filesaver').saveAs;
|
||||
var registry = require('plugins/kibana/settings/saved_object_registry');
|
||||
var objectIndexHTML = require('plugins/kibana/settings/sections/objects/_objects.html');
|
||||
const MAX_SIZE = Math.pow(2, 31) - 1;
|
||||
|
||||
require('ui/directives/file_upload');
|
||||
|
||||
|
@ -91,11 +92,12 @@ define(function (require) {
|
|||
retrieveAndExportDocs(objs);
|
||||
};
|
||||
|
||||
$scope.exportAll = function () {
|
||||
var objs = $scope.services.map(function (service) {
|
||||
return service.data.map(_.partialRight(_.extend, {type: service.type}));
|
||||
});
|
||||
retrieveAndExportDocs(_.flattenDeep(objs));
|
||||
$scope.exportAll = () => {
|
||||
Promise.map($scope.services, (service) =>
|
||||
service.service.find('', MAX_SIZE).then((results) =>
|
||||
results.hits.map((hit) => _.extend(hit, {type: service.type}))
|
||||
)
|
||||
).then((results) => retrieveAndExportDocs(_.flattenDeep(results)));
|
||||
};
|
||||
|
||||
function retrieveAndExportDocs(objs) {
|
||||
|
|
|
@ -24,20 +24,19 @@ kbn-settings-objects {
|
|||
margin-bottom: @line-height-computed;
|
||||
}
|
||||
.list-unstyled {
|
||||
// border-top: 1px solid @gray-lighter;
|
||||
// margin-top: @line-height-computed;
|
||||
li {
|
||||
border-bottom: 1px solid @gray-lighter;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: @settings-objects-list-border;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
color: @gray-light;
|
||||
color: @settings-objects-empty-color;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
margin-top: 10px;
|
||||
background-color: @gray-lighter;
|
||||
background-color: @settings-objects-action-bar-bg;
|
||||
padding: 4px 12px;
|
||||
|
||||
label {
|
||||
|
@ -98,7 +97,7 @@ kbn-settings-objects-view {
|
|||
width: 100%;
|
||||
|
||||
tr.default td.value {
|
||||
color: @input-color-placeholder;
|
||||
color: @settings-advanced-table-default-color;
|
||||
}
|
||||
|
||||
td {
|
||||
|
@ -127,7 +126,7 @@ kbn-settings-objects-view {
|
|||
|
||||
.indices-settings {
|
||||
i.active {
|
||||
color: @btn-success-bg;
|
||||
color: @settings-indices-active-color;
|
||||
}
|
||||
|
||||
tr.field-settings {
|
||||
|
|
|
@ -126,7 +126,9 @@
|
|||
|
||||
<div class="vis-editor-content">
|
||||
|
||||
<vis-editor-sidebar class="vis-editor-sidebar" ng-if="chrome.getVisible()"></vis-editor-sidebar>
|
||||
<div class="collapsible-sidebar">
|
||||
<vis-editor-sidebar class="vis-editor-sidebar" ng-if="chrome.getVisible()"></vis-editor-sidebar>
|
||||
</div>
|
||||
|
||||
<div class="vis-editor-canvas" ng-class="{ embedded: !chrome.getVisible() }">
|
||||
<div class="visualize-info" ng-if="savedVis.id">
|
||||
|
|
|
@ -5,7 +5,7 @@ define(function (require) {
|
|||
require('plugins/kibana/visualize/editor/agg_filter');
|
||||
|
||||
require('ui/visualize');
|
||||
require('ui/clipboard');
|
||||
require('ui/collapsible_sidebar');
|
||||
|
||||
require('ui/routes')
|
||||
.when('/visualize/create', {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
<div class="form-group">
|
||||
<label>
|
||||
Embed this visualization.
|
||||
<kbn-clipboard copy="conf.shareData().embed"></kbn-clipboard>
|
||||
<small>Add to your html source. Note all clients must still be able to access kibana</small>
|
||||
</label>
|
||||
<div class="form-control" disabled>{{conf.shareData().embed}}</div>
|
||||
|
@ -15,7 +14,6 @@
|
|||
<div class="form-group">
|
||||
<label>
|
||||
Share a link
|
||||
<kbn-clipboard copy="conf.shareData().link"></kbn-clipboard>
|
||||
</label>
|
||||
<div class="form-control" disabled>{{conf.shareData().link}}</div>
|
||||
</div>
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
.navbar-default .navbar-nav {
|
||||
&> .active > a:before {
|
||||
border: 7px solid transparent;
|
||||
border-bottom-color: @gray-lighter;
|
||||
border-bottom-color: @vis-editor-navbar-current-tab-color;
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: @btn-danger-color;
|
||||
background-color: @btn-danger-bg;
|
||||
color: @vis-editor-navbar-error-state-color;
|
||||
background-color: @vis-editor-navbar-error-state-bg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,8 +35,8 @@
|
|||
right: 0px;
|
||||
bottom: 0px;
|
||||
z-index: 10;
|
||||
background: fadeout(@navbar-default-bg, 10%);
|
||||
color: white;
|
||||
background: fadeout(@vis-editor-navbar-modal-bg, 10%);
|
||||
color: @vis-editor-navbar-modal-color;
|
||||
text-align: center;
|
||||
padding-top: 6px;
|
||||
|
||||
|
@ -50,7 +50,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
color: @vis-editor-navbar-modal-link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,17 +64,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-sidebar {
|
||||
.collapsible-sidebar {
|
||||
.flex-parent(0, 0, auto);
|
||||
}
|
||||
|
||||
// overflow: auto;
|
||||
&-sidebar {
|
||||
.flex-parent(1, 0, auto);
|
||||
|
||||
// overrided for tablet and desktop
|
||||
@media (min-width: @screen-md-min) {
|
||||
flex-basis: @vis-editor-sidebar-basis;
|
||||
min-width: @vis-editor-sidebar-min-width;
|
||||
max-width: @vis-editor-sidebar-min-width;
|
||||
// margin-bottom: (@input-height-base * 2) - 3;
|
||||
}
|
||||
|
||||
nav {
|
||||
|
@ -87,8 +88,7 @@
|
|||
|
||||
.sidebar-container {
|
||||
.flex-parent(1, 1, auto);
|
||||
background-color: @body-bg;
|
||||
border-right-color: @sidebar-bg;
|
||||
background-color: @vis-editor-sidebar-bg;
|
||||
|
||||
form {
|
||||
.flex-parent(1, 1, auto);
|
||||
|
@ -117,14 +117,14 @@
|
|||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border: inherit !important;
|
||||
background-color: @gray-lighter;
|
||||
background-color: @vis-editor-sidebar-title-bg;
|
||||
margin-bottom: @vis-editor-agg-editor-spacing;
|
||||
padding: 2px 5px !important;
|
||||
}
|
||||
|
||||
.sidebar-item-title:hover {
|
||||
color: @text-color !important;
|
||||
background-color: @gray-lighter !important;
|
||||
color: @vis-editor-sidebar-title-hover-color !important;
|
||||
background-color: @vis-editor-sidebar-title-hover-bg !important;
|
||||
}
|
||||
|
||||
.hintbox {
|
||||
|
@ -158,7 +158,7 @@
|
|||
|
||||
> span {
|
||||
width: @vis-editor-nesting-width;
|
||||
background-color: @brand-success;
|
||||
background-color: @vis-editor-nesting-indicator-bg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@
|
|||
|
||||
&-group {
|
||||
.flex-parent(0, 1, auto);
|
||||
color: @text-color;
|
||||
color: @vis-editor-agg-group-color;
|
||||
}
|
||||
|
||||
&-header {
|
||||
|
@ -221,8 +221,8 @@
|
|||
margin: @vis-editor-agg-editor-spacing 0;
|
||||
padding: @vis-editor-agg-editor-spacing;
|
||||
text-align: center;
|
||||
background: @btn-danger-bg;
|
||||
color: @btn-danger-color;
|
||||
background: @vis-editor-agg-error-bg;
|
||||
color: @vis-editor-agg-error-color;
|
||||
}
|
||||
|
||||
&-editor {
|
||||
|
@ -241,7 +241,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: @link-color;
|
||||
color: @vis-editor-agg-editor-flags-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,23 +275,26 @@
|
|||
|
||||
&-wide-btn {
|
||||
border-radius: 0;
|
||||
border-top: 2px solid @gray-lighter;
|
||||
border-top: 2px solid;
|
||||
border-top-color: @vis-editor-agg-wide-btn-border;
|
||||
|
||||
&-add {
|
||||
width: 140px;
|
||||
margin: -2px auto 5px auto;
|
||||
text-align: center;
|
||||
border: 2px solid @gray-lighter;
|
||||
border: 2px solid;
|
||||
border-color: @vis-editor-agg-wide-btn-border;
|
||||
border-top: 0px;
|
||||
padding: 3px;
|
||||
border-bottom-right-radius: @border-radius-base;
|
||||
border-bottom-left-radius: @border-radius-base;
|
||||
background-color: @body-bg;
|
||||
background-color: @vis-editor-agg-wide-btn-bg;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-add:hover {
|
||||
background-color: @gray-lighter;
|
||||
color: @vis-editor-agg-wide-btn-hover-color;
|
||||
background-color: @vis-editor-agg-wide-btn-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,8 +313,10 @@
|
|||
}
|
||||
|
||||
&-order-agg {
|
||||
border: 2px solid @gray-lighter;
|
||||
border: 2px solid;
|
||||
border-color: @vis-editor-agg-editor-order-border;
|
||||
border-radius: @border-radius-base;
|
||||
background-color: @vis-editor-agg-editor-order-bg;
|
||||
margin: @vis-editor-agg-editor-spacing;
|
||||
padding: @vis-editor-agg-editor-spacing;
|
||||
}
|
||||
|
@ -326,6 +331,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding-left: @collapser-width;
|
||||
|
||||
&.embedded {
|
||||
flex-shrink: 1;
|
||||
|
|
|
@ -41,7 +41,7 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
this.find = function (searchString) {
|
||||
this.find = function (searchString, size = 100) {
|
||||
var self = this;
|
||||
var body;
|
||||
if (searchString) {
|
||||
|
@ -62,7 +62,7 @@ define(function (require) {
|
|||
index: kbnIndex,
|
||||
type: 'visualization',
|
||||
body: body,
|
||||
size: 100,
|
||||
size: size
|
||||
})
|
||||
.then(function (resp) {
|
||||
return {
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
&-description {
|
||||
flex: 1 1 auto;
|
||||
color: @brand-primary;
|
||||
color: @wizard-vis-type-description-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
|||
align-self: flex-end;
|
||||
|
||||
&-tab {
|
||||
background-color: @well-bg;
|
||||
background-color: @visualize-info-bg;
|
||||
padding: 5px 10px;
|
||||
margin-left: @padding-base-horizontal;
|
||||
border-bottom-left-radius: @border-radius-base;
|
||||
|
|
|
@ -5,6 +5,7 @@ module.exports = function (kibana) {
|
|||
title: 'Server Status',
|
||||
main: 'plugins/statusPage/statusPage',
|
||||
hidden: true,
|
||||
url: '/status',
|
||||
|
||||
autoload: [].concat(
|
||||
kibana.autoload.styles,
|
||||
|
@ -15,4 +16,3 @@ module.exports = function (kibana) {
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module.exports = function ({env, bundle}) {
|
|||
.join('\n');
|
||||
|
||||
let requires = bundle.modules
|
||||
.map(m => `require('${m}');`)
|
||||
.map(m => `require(${JSON.stringify(m)});`)
|
||||
.join('\n');
|
||||
|
||||
return `
|
||||
|
@ -21,6 +21,8 @@ ${pluginSlug}
|
|||
*/
|
||||
|
||||
window.__KBN__ = {
|
||||
version: '1.2.3',
|
||||
buildNum: 1234,
|
||||
vars: {
|
||||
kbnIndex: '.kibana',
|
||||
esShardTimeout: 1500,
|
||||
|
|
|
@ -87,6 +87,7 @@ module.exports = Joi.object({
|
|||
lazyHost: Joi.string().hostname().default('localhost'),
|
||||
lazyPrebuild: Joi.boolean().default(false),
|
||||
lazyProxyTimeout: Joi.number().default(5 * 60000),
|
||||
useBundleCache: Joi.boolean().default(Joi.ref('$prod')),
|
||||
unsafeCache: Joi
|
||||
.alternatives()
|
||||
.try(
|
||||
|
|
|
@ -7,5 +7,5 @@ module.exports = _.once(function (kbnServer) {
|
|||
|
||||
// redirect to the single app
|
||||
let apps = kbnServer.uiExports.apps.toArray();
|
||||
return apps.length === 1 ? `/app/${apps[0].id}` : '/apps';
|
||||
return '/app/kibana';
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
let Stream = require('stream');
|
||||
let moment = require('moment');
|
||||
let _ = require('lodash');
|
||||
let numeral = require('numeral');
|
||||
let numeral = require('@spalger/numeral');
|
||||
let ansicolors = require('ansicolors');
|
||||
let stringify = require('json-stringify-safe');
|
||||
let querystring = require('querystring');
|
||||
|
|
|
@ -19,6 +19,7 @@ class UiApp {
|
|||
this.hidden = this.spec.hidden;
|
||||
this.autoloadOverrides = this.spec.autoload;
|
||||
this.templateName = this.spec.templateName || 'uiApp';
|
||||
this.url = this.spec.url || '/app/' + this.id;
|
||||
|
||||
// once this resolves, no reason to run it again
|
||||
this.getModules = _.once(this.getModules);
|
||||
|
@ -39,7 +40,7 @@ class UiApp {
|
|||
}
|
||||
|
||||
toJSON() {
|
||||
return _.pick(this, ['id', 'title', 'description', 'icon', 'main']);
|
||||
return _.pick(this, ['id', 'title', 'description', 'icon', 'main', 'url']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,33 +40,13 @@ module.exports = async (kbnServer, server, config) => {
|
|||
server.setupViews(resolve(__dirname, 'views'));
|
||||
server.exposeStaticFile('/loading.gif', resolve(__dirname, 'public/loading.gif'));
|
||||
|
||||
// serve the app switcher
|
||||
server.route({
|
||||
path: '/apps',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
let switcher = uiExports.getHiddenApp('appSwitcher');
|
||||
if (!switcher) return reply(Boom.notFound('app switcher not installed'));
|
||||
return reply.renderApp(switcher);
|
||||
}
|
||||
});
|
||||
|
||||
// serve the app switcher
|
||||
server.route({
|
||||
path: '/api/apps',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
return reply(uiExports.apps);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/app/{id}',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
let id = req.params.id;
|
||||
let app = uiExports.apps.byId[id];
|
||||
if (!app) return reply(Boom.notFound('Unkown app ' + id));
|
||||
if (!app) return reply(Boom.notFound('Unknown app ' + id));
|
||||
|
||||
if (kbnServer.status.isGreen()) {
|
||||
return reply.renderApp(app);
|
||||
|
@ -79,7 +59,7 @@ module.exports = async (kbnServer, server, config) => {
|
|||
server.decorate('reply', 'renderApp', function (app) {
|
||||
let payload = {
|
||||
app: app,
|
||||
appCount: uiExports.apps.size,
|
||||
nav: uiExports.apps,
|
||||
version: kbnServer.version,
|
||||
buildNum: config.get('pkg.buildNum'),
|
||||
buildSha: config.get('pkg.buildSha'),
|
||||
|
|
|
@ -16,7 +16,7 @@ define(function () {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the aggConfigResult and parents up te branch
|
||||
* Returns an array of the aggConfigResult and parents up the branch
|
||||
* @returns {array} Array of aggConfigResults
|
||||
*/
|
||||
AggConfigResult.prototype.getPath = function () {
|
||||
|
|
17
src/ui/public/__tests__/metadata.js
Normal file
17
src/ui/public/__tests__/metadata.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
describe('ui/metadata', () => {
|
||||
const expect = require('expect.js');
|
||||
|
||||
const metadata = require('ui/metadata');
|
||||
|
||||
it('is same data as window.__KBN__', () => {
|
||||
expect(metadata.version).to.equal(window.__KBN__.version);
|
||||
expect(metadata.vars.kbnIndex).to.equal(window.__KBN__.vars.kbnIndex);
|
||||
});
|
||||
|
||||
it('is immutable', () => {
|
||||
expect(() => metadata.foo = 'something').to.throw;
|
||||
expect(() => metadata.version = 'something').to.throw;
|
||||
expect(() => metadata.vars = {}).to.throw;
|
||||
expect(() => metadata.vars.kbnIndex = 'something').to.throw;
|
||||
});
|
||||
});
|
|
@ -14,61 +14,79 @@ describe('getPoint', function () {
|
|||
getPoint = Private(require('ui/agg_response/point_series/_get_point'));
|
||||
}));
|
||||
|
||||
it('properly unwraps and scales values without a series', function () {
|
||||
var row = [ { value: 1 }, { value: 2 }, { value: 3 } ];
|
||||
var xAspect = { i: 0 };
|
||||
var seriesAspect = null;
|
||||
var yScale = 5;
|
||||
var yAspect = { i: 1 };
|
||||
var zAspect = { i: 2 };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect, zAspect);
|
||||
describe('Without series aspect', function () {
|
||||
var seriesAspect;
|
||||
var xAspect;
|
||||
var yAspect;
|
||||
var yScale;
|
||||
|
||||
expect(point)
|
||||
.to.have.property('x', 1)
|
||||
.and.have.property('y', 10)
|
||||
.and.have.property('z', 3)
|
||||
.and.have.property('aggConfigResult', row[1])
|
||||
.and.not.have.property('series');
|
||||
beforeEach(function () {
|
||||
seriesAspect = null;
|
||||
xAspect = { i: 0 };
|
||||
yAspect = { i: 1 };
|
||||
yScale = 5;
|
||||
});
|
||||
|
||||
it('properly unwraps and scales values', function () {
|
||||
var row = [ { value: 1 }, { value: 2 }, { value: 3 } ];
|
||||
var zAspect = { i: 2 };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect, zAspect);
|
||||
|
||||
expect(point)
|
||||
.to.have.property('x', 1)
|
||||
.and.have.property('y', 10)
|
||||
.and.have.property('z', 3)
|
||||
.and.have.property('aggConfigResult', row[1])
|
||||
.and.not.have.property('series');
|
||||
});
|
||||
|
||||
it('ignores points with a y value of NaN', function () {
|
||||
var row = [ { value: 1 }, { value: 'NaN' }];
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
expect(point).to.be(void 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('properly unwraps and scales values with a series', function () {
|
||||
var row = [ { value: 1 }, { value: 2 }, { value: 3 }];
|
||||
var xAspect = { i: 0 };
|
||||
var seriesAspect = { i: 1, agg: identFormatted };
|
||||
var yScale = null;
|
||||
var yAspect = { i: 2 };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
describe('With series aspect', function () {
|
||||
var row;
|
||||
var xAspect;
|
||||
var yAspect;
|
||||
var yScale;
|
||||
|
||||
expect(point)
|
||||
.to.have.property('x', 1)
|
||||
.and.have.property('series', 2)
|
||||
.and.have.property('y', 3)
|
||||
.and.have.property('aggConfigResult', row[2]);
|
||||
beforeEach(function () {
|
||||
row = [ { value: 1 }, { value: 2 }, { value: 3 }];
|
||||
xAspect = { i: 0 };
|
||||
yAspect = { i: 2 };
|
||||
yScale = null;
|
||||
});
|
||||
|
||||
it('properly unwraps and scales values', function () {
|
||||
var seriesAspect = { i: 1, agg: identFormatted };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
|
||||
expect(point)
|
||||
.to.have.property('x', 1)
|
||||
.and.have.property('series', 2)
|
||||
.and.have.property('y', 3)
|
||||
.and.have.property('aggConfigResult', row[2]);
|
||||
});
|
||||
|
||||
it('properly formats series values', function () {
|
||||
var seriesAspect = { i: 1, agg: truthFormatted };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
|
||||
expect(point)
|
||||
.to.have.property('x', 1)
|
||||
.and.have.property('series', true)
|
||||
.and.have.property('y', 3)
|
||||
.and.have.property('aggConfigResult', row[2]);
|
||||
});
|
||||
|
||||
it ('adds the aggConfig to the points', function () {
|
||||
var seriesAspect = { i: 1, agg: truthFormatted};
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
|
||||
expect(point).to.have.property('aggConfig', truthFormatted);
|
||||
});
|
||||
});
|
||||
|
||||
it('properly formats series values', function () {
|
||||
var row = [ { value: 1 }, { value: 2 }, { value: 3 } ];
|
||||
var xAspect = { i: 0 };
|
||||
var seriesAspect = { i: 1, agg: truthFormatted };
|
||||
var yScale = null;
|
||||
var yAspect = { i: 2 };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
|
||||
expect(point)
|
||||
.to.have.property('x', 1)
|
||||
.and.have.property('series', true)
|
||||
.and.have.property('y', 3)
|
||||
.and.have.property('aggConfigResult', row[2]);
|
||||
});
|
||||
|
||||
it('ignores points with a y value of NaN', function () {
|
||||
var row = [ { value: 1 }, { value: 'NaN' }];
|
||||
var xAspect = { i: 0 };
|
||||
var seriesAspect = null;
|
||||
var yScale = 5;
|
||||
var yAspect = { i: 1 };
|
||||
var point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||
expect(point).to.be(void 0);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ define(function (require) {
|
|||
}
|
||||
|
||||
if (series) {
|
||||
point.aggConfig = series.agg;
|
||||
point.series = series.agg.fieldFormatter()(unwrap(row[series.i]));
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ define(function (require) {
|
|||
|
||||
var series = _(rows)
|
||||
.transform(function (series, row) {
|
||||
|
||||
if (!multiY) {
|
||||
var point = partGetPoint(row, aspects.y, aspects.z);
|
||||
if (point) addToSiri(series, point, point.series);
|
||||
|
|
|
@ -15,15 +15,11 @@ kbn-agg-table-group {
|
|||
overflow: auto;
|
||||
|
||||
tr:hover td {
|
||||
background-color: lighten(@gray-lighter, 4%);
|
||||
background-color: @table-row-hover-bg;
|
||||
}
|
||||
|
||||
.cell-hover:hover {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
|
||||
th i.fa-sort {
|
||||
color: @gray-light;
|
||||
background-color: @table-cell-hover-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,71 @@
|
|||
var _ = require('lodash');
|
||||
var storage = window.sessionStorage;
|
||||
const _ = require('lodash');
|
||||
const reEsc = require('lodash').escapeRegExp;
|
||||
const { parse, format } = require('url');
|
||||
|
||||
function Tab(spec) {
|
||||
this.id = spec.id;
|
||||
this.title = spec.title;
|
||||
this.active = false;
|
||||
this.resetWhenActive = !!spec.resetWhenActive;
|
||||
this.lastUrlStoreKey = spec.trackLastPath ? 'lastUrl:' + this.id : null;
|
||||
this.rootUrl = '/' + this.id;
|
||||
this.lastUrl = this.lastUrlStoreKey && storage.getItem(this.lastUrlStoreKey);
|
||||
const urlJoin = (a, b) => {
|
||||
if (!b) return a;
|
||||
return `${a}${ a.endsWith('/') ? '' : '/' }${b}`;
|
||||
};
|
||||
|
||||
this.activeIndicatorColor = spec.activeIndicatorColor || null;
|
||||
if (_.isFunction(this.activeIndicatorColor)) {
|
||||
// convert to a getter
|
||||
Object.defineProperty(this, 'activeIndicatorColor', {
|
||||
get: this.activeIndicatorColor
|
||||
});
|
||||
export default class Tab {
|
||||
constructor(spec = {}) {
|
||||
this.id = spec.id || '';
|
||||
this.title = spec.title || '';
|
||||
this.resetWhenActive = !!spec.resetWhenActive;
|
||||
this.activeIndicatorColor = spec.activeIndicatorColor || null;
|
||||
if (_.isFunction(this.activeIndicatorColor)) {
|
||||
// convert to a getter
|
||||
Object.defineProperty(this, 'activeIndicatorColor', {
|
||||
get: this.activeIndicatorColor
|
||||
});
|
||||
}
|
||||
|
||||
this.active = false;
|
||||
|
||||
this.baseUrl = spec.baseUrl || '/';
|
||||
this.rootUrl = urlJoin(this.baseUrl, this.id);
|
||||
this.rootRegExp = new RegExp(`^${reEsc(this.rootUrl)}(/|$|\\?|#)`);
|
||||
|
||||
this.lastUrlStoreKey = `lastUrl:${this.id}`;
|
||||
this.lastUrlStore = spec.lastUrlStore;
|
||||
this.lastUrl = this.lastUrlStore ? this.lastUrlStore.getItem(this.lastUrlStoreKey) : null;
|
||||
}
|
||||
|
||||
href() {
|
||||
if (this.active) {
|
||||
return this.resetWhenActive ? this.rootUrl : null;
|
||||
}
|
||||
return this.lastUrl || this.rootUrl;
|
||||
}
|
||||
|
||||
updateLastUrlGlobalState(globalState) {
|
||||
let lastPath = this.getLastPath();
|
||||
let { pathname, query, hash } = parse(lastPath, true);
|
||||
|
||||
query = query || {};
|
||||
if (!globalState) delete query._g;
|
||||
else query._g = globalState;
|
||||
|
||||
this.setLastUrl(`${this.rootUrl}${format({ pathname, query, hash })}`);
|
||||
}
|
||||
|
||||
getLastPath() {
|
||||
let { id, rootUrl } = this;
|
||||
let lastUrl = this.getLastUrl();
|
||||
|
||||
if (!lastUrl.startsWith(rootUrl)) {
|
||||
throw new Error(`Tab "${id}" has invalid root "${rootUrl}" for last url "${lastUrl}"`);
|
||||
}
|
||||
|
||||
return lastUrl.slice(rootUrl.length);
|
||||
}
|
||||
|
||||
setLastUrl(url) {
|
||||
this.lastUrl = url;
|
||||
if (this.lastUrlStore) this.lastUrlStore.setItem(this.lastUrlStoreKey, this.lastUrl);
|
||||
}
|
||||
|
||||
getLastUrl() {
|
||||
return this.lastUrl || this.rootUrl;
|
||||
}
|
||||
}
|
||||
|
||||
Tab.prototype.persistLastUrl = function (url) {
|
||||
if (!this.lastUrlStoreKey) return;
|
||||
this.lastUrl = url;
|
||||
storage.setItem(this.lastUrlStoreKey, this.lastUrl);
|
||||
};
|
||||
|
||||
Tab.prototype.href = function () {
|
||||
if (this.active) {
|
||||
if (this.resetWhenActive) return '#' + this.rootUrl;
|
||||
return null;
|
||||
}
|
||||
|
||||
return '#' + (this.lastUrl || this.rootUrl);
|
||||
};
|
||||
|
||||
module.exports = Tab;
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
var _ = require('lodash');
|
||||
var { startsWith, get, set, omit, wrap, pick } = require('lodash');
|
||||
var Tab = require('ui/chrome/Tab');
|
||||
var format = require('url').format;
|
||||
var parse = _.wrap(require('url').parse, function (parse, path) {
|
||||
var parsed = parse(path, true);
|
||||
return {
|
||||
pathname: parsed.pathname,
|
||||
query: parsed.query || {},
|
||||
hash: parsed.hash
|
||||
};
|
||||
});
|
||||
|
||||
function TabCollection() {
|
||||
var { parse } = require('url');
|
||||
|
||||
function TabCollection(opts = {}) {
|
||||
var tabs = [];
|
||||
var specs = null;
|
||||
var defaults = null;
|
||||
var defaults = opts.defaults || {};
|
||||
var activeTab = null;
|
||||
|
||||
this.set = function (_specs) {
|
||||
|
@ -23,7 +15,7 @@ function TabCollection() {
|
|||
};
|
||||
|
||||
this.setDefaults = function () {
|
||||
defaults = _.clone(arguments[0]);
|
||||
defaults = _.defaults({}, arguments[0], defaults);
|
||||
this._rebuildTabs();
|
||||
};
|
||||
|
||||
|
@ -42,20 +34,19 @@ function TabCollection() {
|
|||
return activeTab;
|
||||
};
|
||||
|
||||
this.consumeRouteUpdate = function ($location, persist) {
|
||||
var url = parse($location.url(), true);
|
||||
var id = $location.path().split('/')[1] || '';
|
||||
|
||||
this.consumeRouteUpdate = function (href, persist) {
|
||||
tabs.forEach(function (tab) {
|
||||
var active = tab.active = (tab.id === id);
|
||||
var lastUrl = active ? url : parse(tab.lastUrl || tab.rootUrl);
|
||||
lastUrl.query._g = url.query._g;
|
||||
|
||||
if (tab.active) activeTab = tab;
|
||||
if (persist) {
|
||||
tab.persistLastUrl(format(lastUrl));
|
||||
tab.active = tab.rootRegExp.test(href);
|
||||
if (tab.active) {
|
||||
activeTab = tab;
|
||||
activeTab.setLastUrl(href);
|
||||
}
|
||||
});
|
||||
|
||||
if (!persist || !activeTab) return;
|
||||
|
||||
let globalState = get(parse(activeTab.getLastPath(), true), 'query._g');
|
||||
tabs.forEach(tab => tab.updateLastUrlGlobalState(globalState));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
241
src/ui/public/chrome/__tests__/Tab.js
Normal file
241
src/ui/public/chrome/__tests__/Tab.js
Normal file
|
@ -0,0 +1,241 @@
|
|||
const Tab = require('../Tab');
|
||||
const expect = require('expect.js');
|
||||
const TabFakeStore = require('./_TabFakeStore');
|
||||
|
||||
describe('Chrome Tab', function () {
|
||||
describe('construction', function () {
|
||||
it('accepts id, title, resetWhenActive, trackLastUrl, activeIndicatorColor, baseUrl', function () {
|
||||
const tab = new Tab({
|
||||
id: 'foo',
|
||||
title: 'Foo App',
|
||||
resetWhenActive: false,
|
||||
activeIndicatorColor: true,
|
||||
baseUrl: 'proto:host.domain:999'
|
||||
});
|
||||
|
||||
expect(tab.id).to.equal('foo');
|
||||
expect(tab.title).to.equal('Foo App');
|
||||
expect(tab.resetWhenActive).to.equal(false);
|
||||
expect(tab.activeIndicatorColor).to.equal(true);
|
||||
expect(tab.rootUrl).to.equal('proto:host.domain:999/foo');
|
||||
|
||||
const tab2 = new Tab({
|
||||
id: 'bar',
|
||||
title: 'Bar App',
|
||||
resetWhenActive: true,
|
||||
activeIndicatorColor: false,
|
||||
baseUrl: 'proto:host.domain:999/sub/#/'
|
||||
});
|
||||
|
||||
expect(tab2.id).to.equal('bar');
|
||||
expect(tab2.title).to.equal('Bar App');
|
||||
expect(tab2.resetWhenActive).to.equal(true);
|
||||
expect(tab2.activeIndicatorColor).to.equal(null);
|
||||
expect(tab2.rootUrl).to.equal('proto:host.domain:999/sub/#/bar');
|
||||
});
|
||||
|
||||
it('starts inactive', function () {
|
||||
const tab = new Tab();
|
||||
expect(tab.active).to.equal(false);
|
||||
});
|
||||
|
||||
it('uses the id to set the rootUrl', function () {
|
||||
const id = 'foo';
|
||||
const tab = new Tab({ id });
|
||||
expect(tab.id).to.equal(id);
|
||||
expect(tab.rootUrl).to.equal(`/${id}`);
|
||||
});
|
||||
|
||||
it('creates a regexp for matching the rootUrl', function () {
|
||||
const tab = new Tab({ id: 'foo' });
|
||||
|
||||
expect('/foo').to.match(tab.rootRegExp);
|
||||
expect('/foo/bar').to.match(tab.rootRegExp);
|
||||
expect('/foo/bar/max').to.match(tab.rootRegExp);
|
||||
expect('/foo?bar=baz').to.match(tab.rootRegExp);
|
||||
expect('/foo/?bar=baz').to.match(tab.rootRegExp);
|
||||
expect('/foo#?bar=baz').to.match(tab.rootRegExp);
|
||||
|
||||
expect('/foobar').to.not.match(tab.rootRegExp);
|
||||
expect('site.com/foo#?bar=baz').to.not.match(tab.rootRegExp);
|
||||
expect('http://site.com/foo#?bar=baz').to.not.match(tab.rootRegExp);
|
||||
});
|
||||
|
||||
it('includes the baseUrl in the rootRegExp if specified', function () {
|
||||
const tab = new Tab({
|
||||
id: 'foo',
|
||||
baseUrl: 'http://spiderman.com/kibana'
|
||||
});
|
||||
|
||||
expect('http://spiderman.com/kibana/foo/bar').to.match(tab.rootRegExp);
|
||||
|
||||
expect('/foo').to.not.match(tab.rootRegExp);
|
||||
expect('https://spiderman.com/kibana/foo/bar').to.not.match(tab.rootRegExp);
|
||||
});
|
||||
|
||||
it('accepts a function for activeIndicatorColor', function () {
|
||||
let i = 0;
|
||||
const tab = new Tab({
|
||||
activeIndicatorColor: function () {
|
||||
return i++;
|
||||
}
|
||||
});
|
||||
expect(tab.activeIndicatorColor).to.equal(0);
|
||||
expect(tab.activeIndicatorColor).to.equal(1);
|
||||
expect(tab.activeIndicatorColor).to.equal(2);
|
||||
expect(tab.activeIndicatorColor).to.equal(3);
|
||||
});
|
||||
|
||||
it('discovers the lastUrl', function () {
|
||||
const lastUrlStore = new TabFakeStore();
|
||||
const tab = new Tab({ id: 'foo', lastUrlStore });
|
||||
expect(tab.lastUrl).to.not.equal('bar');
|
||||
|
||||
tab.setLastUrl('bar');
|
||||
expect(tab.lastUrl).to.equal('bar');
|
||||
|
||||
const tab2 = new Tab({ id: 'foo', lastUrlStore });
|
||||
expect(tab2.lastUrl).to.equal('bar');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#setLastUrl()', function () {
|
||||
it('updates the lastUrl and storage value if passed a lastUrlStore', function () {
|
||||
const lastUrlStore = new TabFakeStore();
|
||||
const tab = new Tab({ id: 'foo', lastUrlStore });
|
||||
|
||||
expect(tab.lastUrl).to.not.equal('foo');
|
||||
tab.setLastUrl('foo');
|
||||
expect(tab.lastUrl).to.equal('foo');
|
||||
expect(lastUrlStore.getItem(tab.lastUrlStoreKey)).to.equal('foo');
|
||||
});
|
||||
|
||||
it('only updates lastUrl if no lastUrlStore', function () {
|
||||
const tab = new Tab({ id: 'foo' });
|
||||
|
||||
expect(tab.lastUrl).to.equal(null);
|
||||
tab.setLastUrl('foo');
|
||||
expect(tab.lastUrl).to.equal('foo');
|
||||
|
||||
const tab2 = new Tab({ id: 'foo' });
|
||||
expect(tab2.lastUrl).to.not.equal('foo');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#href()', function () {
|
||||
it('returns the rootUrl/id be default', function () {
|
||||
const tab = new Tab({ id: 'foo' });
|
||||
expect(tab.href()).to.equal(tab.rootUrl);
|
||||
});
|
||||
|
||||
it('returns the lastUrl if tracking is on', function () {
|
||||
const tab = new Tab({ id: 'foo' });
|
||||
tab.setLastUrl('okay');
|
||||
expect(tab.href()).to.equal('okay');
|
||||
});
|
||||
|
||||
describe('when the tab is active', function () {
|
||||
it('returns the rootUrl when resetWhenActive: true', function () {
|
||||
const id = 'foo';
|
||||
const resetWhenActive = true;
|
||||
const tab = new Tab({ id, resetWhenActive });
|
||||
|
||||
tab.active = true;
|
||||
|
||||
expect(tab.href()).to.not.equal('butt');
|
||||
expect(tab.href()).to.equal(tab.rootUrl);
|
||||
});
|
||||
|
||||
it('or returns null when not', function () {
|
||||
const tab = new Tab({ id: 'foo', resetWhenActive: false });
|
||||
tab.active = true;
|
||||
|
||||
expect(tab.href()).to.not.equal('butt');
|
||||
expect(tab.href()).to.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getLastPath()', function () {
|
||||
it('parses a path out of the lastUrl by removing the baseUrl', function () {
|
||||
const baseUrl = 'http://local:5601/app/visualize#';
|
||||
const tab = new Tab({ baseUrl });
|
||||
|
||||
tab.setLastUrl('http://local:5601/app/visualize#/index');
|
||||
expect(tab.getLastPath()).to.equal('/index');
|
||||
});
|
||||
|
||||
it('throws an error if the lastUrl does not extend the root url', function () {
|
||||
expect(function () {
|
||||
const baseUrl = 'http://local:5601/app/visualize#';
|
||||
const tab = new Tab({ baseUrl });
|
||||
|
||||
tab.setLastUrl('http://local:5601/');
|
||||
tab.getLastPath();
|
||||
}).to.throwError(/invalid.*root/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateLastUrlGlobalState', function () {
|
||||
const bases = [
|
||||
'http://local:5601',
|
||||
'',
|
||||
'weird.domain/with/subpath?path#',
|
||||
'weird.domain/with/#hashpath',
|
||||
];
|
||||
|
||||
context('with new state sets _g properly', function () {
|
||||
const paths = [
|
||||
[ '/', '/?_g=newState' ],
|
||||
[ '/?first', '/?first=&_g=newState' ],
|
||||
[ '/path?first=1&_g=afterHash', '/path?first=1&_g=newState' ],
|
||||
[ '/?first=1&_g=second', '/?first=1&_g=newState' ],
|
||||
[ '/?g=first', '/?g=first&_g=newState' ],
|
||||
[ '/a?first=1&_g=second', '/a?first=1&_g=newState' ],
|
||||
[ '/?first=1&_g=second', '/?first=1&_g=newState' ],
|
||||
[ '/?first&g=second', '/?first=&g=second&_g=newState' ],
|
||||
];
|
||||
|
||||
bases.forEach(baseUrl => {
|
||||
paths.forEach(([pathFrom, pathTo]) => {
|
||||
const fromUrl = `${baseUrl}${pathFrom}`;
|
||||
const toUrl = `${baseUrl}${pathTo}`;
|
||||
it(`${fromUrl} => ${toUrl}`, function () {
|
||||
const tab = new Tab({ baseUrl });
|
||||
tab.setLastUrl(fromUrl);
|
||||
tab.updateLastUrlGlobalState('newState');
|
||||
expect(tab.getLastUrl()).to.equal(toUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with new empty state removes _g', function () {
|
||||
const paths = [
|
||||
[ '/', '/' ],
|
||||
[ '/?first', '/?first=' ],
|
||||
[ '/path?first=1&_g=afterHash', '/path?first=1' ],
|
||||
[ '/?first=1&_g=second', '/?first=1' ],
|
||||
[ '/?g=first', '/?g=first' ],
|
||||
[ '/a?first=1&_g=second', '/a?first=1' ],
|
||||
[ '/?first=1&_g=second', '/?first=1' ],
|
||||
[ '/?first&g=second', '/?first=&g=second' ],
|
||||
];
|
||||
|
||||
bases.forEach(baseUrl => {
|
||||
paths.forEach(([pathFrom, pathTo]) => {
|
||||
const fromUrl = `${baseUrl}${pathFrom}`;
|
||||
const toUrl = `${baseUrl}${pathTo}`;
|
||||
it(`${fromUrl}`, function () {
|
||||
const tab = new Tab({ baseUrl });
|
||||
tab.setLastUrl(fromUrl);
|
||||
tab.updateLastUrlGlobalState();
|
||||
expect(tab.getLastUrl()).to.equal(toUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
73
src/ui/public/chrome/__tests__/TabCollection.js
Normal file
73
src/ui/public/chrome/__tests__/TabCollection.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
const expect = require('expect.js');
|
||||
const { indexBy, random } = require('lodash');
|
||||
|
||||
const TabFakeStore = require('./_TabFakeStore');
|
||||
const TabCollection = require('../TabCollection');
|
||||
const Tab = require('../Tab');
|
||||
|
||||
describe('Chrome TabCollection', function () {
|
||||
describe('empty state', function () {
|
||||
it('has no tabs', function () {
|
||||
const tabs = new TabCollection();
|
||||
expect(tabs.get()).to.eql([]);
|
||||
});
|
||||
|
||||
it('has no active tab', function () {
|
||||
const tabs = new TabCollection();
|
||||
expect(!tabs.getActive()).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#set()', function () {
|
||||
it('consumes an ordered list of Tab specs', function () {
|
||||
const tabs = new TabCollection();
|
||||
tabs.set([
|
||||
{ id: 'foo' },
|
||||
{ id: 'bar' }
|
||||
]);
|
||||
|
||||
const ts = tabs.get();
|
||||
expect(ts.length).to.equal(2);
|
||||
expect(ts[0].id).to.equal('foo');
|
||||
expect(ts[1].id).to.equal('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setDefaults()', function () {
|
||||
it('applies the defaults used to create tabs', function () {
|
||||
const tabs = new TabCollection();
|
||||
tabs.setDefaults({ id: 'thing' });
|
||||
tabs.set([ {} ]);
|
||||
|
||||
expect(tabs.get()[0].id).to.equal('thing');
|
||||
});
|
||||
|
||||
it('recreates existing tabs with new defaults', function () {
|
||||
const tabs = new TabCollection();
|
||||
tabs.set([ {} ]);
|
||||
expect(!tabs.get()[0].id).to.equal(true);
|
||||
|
||||
tabs.setDefaults({ id: 'thing' });
|
||||
expect(tabs.get()[0].id).to.equal('thing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#consumeRouteUpdate()', function () {
|
||||
it('updates the active tab', function () {
|
||||
const store = new TabFakeStore();
|
||||
const baseUrl = `http://localhost:${random(1000, 9999)}`;
|
||||
const tabs = new TabCollection({ store, defaults: { baseUrl } });
|
||||
tabs.set([
|
||||
{ id: 'a' },
|
||||
{ id: 'b' }
|
||||
]);
|
||||
|
||||
tabs.consumeRouteUpdate(`${baseUrl}/a`);
|
||||
const {a, b} = indexBy(tabs.get(), 'id');
|
||||
expect(a.active).to.equal(true);
|
||||
expect(b.active).to.equal(false);
|
||||
expect(tabs.getActive()).to.equal(a);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
9
src/ui/public/chrome/__tests__/_TabFakeStore.js
Normal file
9
src/ui/public/chrome/__tests__/_TabFakeStore.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const store = Symbol('store');
|
||||
|
||||
export default class TabFakeStore {
|
||||
constructor() { this[store] = new Map(); }
|
||||
getItem(k) { return this[store].get(k); }
|
||||
setItem(k, v) { return this[store].set(k, v); }
|
||||
getKeys() { return [ ...this[store].keys() ]; }
|
||||
getValues() { return [ ...this[store].values() ]; }
|
||||
}
|
163
src/ui/public/chrome/api/__tests__/apps.js
Normal file
163
src/ui/public/chrome/api/__tests__/apps.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
const expect = require('expect.js');
|
||||
|
||||
const setup = require('../apps');
|
||||
const TabFakeStore = require('../../__tests__/_TabFakeStore');
|
||||
|
||||
describe('Chrome API :: apps', function () {
|
||||
describe('#get/setShowAppsLink()', function () {
|
||||
describe('defaults to false if there are less than two apps', function () {
|
||||
it('appCount = 0', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, { nav: [] });
|
||||
expect(chrome.getShowAppsLink()).to.equal(false);
|
||||
});
|
||||
|
||||
it('appCount = 1', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, { nav: [ { url: '/' } ] });
|
||||
expect(chrome.getShowAppsLink()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaults to true if there are two or more apps', function () {
|
||||
it('appCount = 2', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, { nav: [ { url: '/' }, { url: '/2' } ] });
|
||||
expect(chrome.getShowAppsLink()).to.equal(true);
|
||||
});
|
||||
|
||||
it('appCount = 3', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, { nav: [ { url: '/' }, { url: '/2' }, { url: '/3' } ] });
|
||||
expect(chrome.getShowAppsLink()).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('is chainable', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, { nav: [ { url: '/' } ] });
|
||||
expect(chrome.setShowAppsLink(true)).to.equal(chrome);
|
||||
});
|
||||
|
||||
it('can be changed', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, { nav: [ { url: '/' } ] });
|
||||
|
||||
expect(chrome.setShowAppsLink(true).getShowAppsLink()).to.equal(true);
|
||||
expect(chrome.getShowAppsLink()).to.equal(true);
|
||||
|
||||
expect(chrome.setShowAppsLink(false).getShowAppsLink()).to.equal(false);
|
||||
expect(chrome.getShowAppsLink()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getApp()', function () {
|
||||
it('returns a clone of the current app', function () {
|
||||
const chrome = {};
|
||||
const app = { url: '/' };
|
||||
setup(chrome, { app });
|
||||
|
||||
expect(chrome.getApp()).to.eql(app);
|
||||
expect(chrome.getApp()).to.not.equal(app);
|
||||
});
|
||||
|
||||
it('returns undefined if no active app', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, {});
|
||||
expect(chrome.getApp()).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAppTitle()', function () {
|
||||
it('returns the title property of the current app', function () {
|
||||
const chrome = {};
|
||||
const app = { url: '/', title: 'foo' };
|
||||
setup(chrome, { app });
|
||||
expect(chrome.getAppTitle()).to.eql('foo');
|
||||
});
|
||||
|
||||
it('returns undefined if no active app', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, {});
|
||||
expect(chrome.getAppTitle()).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAppUrl()', function () {
|
||||
it('returns the resolved url of the current app', function () {
|
||||
const chrome = {};
|
||||
const app = { url: '/foo' };
|
||||
setup(chrome, { app });
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', app.url);
|
||||
expect(chrome.getAppUrl()).to.equal(a.href);
|
||||
});
|
||||
|
||||
it('returns undefined if no active app', function () {
|
||||
const chrome = {};
|
||||
setup(chrome, {});
|
||||
expect(chrome.getAppUrl()).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInjected()', function () {
|
||||
describe('called without args', function () {
|
||||
it('returns a clone of all injectedVars', function () {
|
||||
const chrome = {};
|
||||
const vars = { name: 'foo' };
|
||||
setup(chrome, { vars });
|
||||
expect(chrome.getInjected()).to.eql(vars);
|
||||
expect(chrome.getInjected()).to.not.equal(vars);
|
||||
});
|
||||
});
|
||||
|
||||
describe('called with a var name', function () {
|
||||
it('returns the var at that name', function () {
|
||||
const chrome = {};
|
||||
const vars = { name: 'foo' };
|
||||
setup(chrome, { vars });
|
||||
expect(chrome.getInjected('name')).to.equal('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('called with a var name and default', function () {
|
||||
it('returns the default when the var is undefined', function () {
|
||||
const chrome = {};
|
||||
const vars = { name: undefined };
|
||||
setup(chrome, { vars });
|
||||
expect(chrome.getInjected('name', 'bar')).to.equal('bar');
|
||||
});
|
||||
|
||||
it('returns null when the var is null', function () {
|
||||
const chrome = {};
|
||||
const vars = { name: null };
|
||||
setup(chrome, { vars });
|
||||
expect(chrome.getInjected('name', 'bar')).to.equal(null);
|
||||
});
|
||||
|
||||
it('returns var if not undefined', function () {
|
||||
const chrome = {};
|
||||
const vars = { name: 'kim' };
|
||||
setup(chrome, { vars });
|
||||
expect(chrome.getInjected('name', 'bar')).to.equal('kim');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get/setLastUrlFor()', function () {
|
||||
it('reads/writes last url from storage', function () {
|
||||
const chrome = {};
|
||||
const store = new TabFakeStore();
|
||||
setup(chrome, { appUrlStore: store });
|
||||
expect(chrome.getLastUrlFor('app')).to.equal(undefined);
|
||||
chrome.setLastUrlFor('app', 'url');
|
||||
expect(chrome.getLastUrlFor('app')).to.equal('url');
|
||||
expect(store.getKeys().length).to.equal(1);
|
||||
expect(store.getValues().shift()).to.equal('url');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
17
src/ui/public/chrome/api/angular.js
vendored
17
src/ui/public/chrome/api/angular.js
vendored
|
@ -1,7 +1,11 @@
|
|||
var modules = require('ui/modules');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
require('../appSwitcher');
|
||||
var modules = require('ui/modules');
|
||||
var ConfigTemplate = require('ui/ConfigTemplate');
|
||||
require('ui/directives/config');
|
||||
|
||||
module.exports = function (chrome, internals) {
|
||||
chrome.setupAngular = function () {
|
||||
var kibana = modules.get('kibana');
|
||||
|
@ -44,7 +48,13 @@ module.exports = function (chrome, internals) {
|
|||
chrome.setVisible(!Boolean($location.search().embed));
|
||||
|
||||
// listen for route changes, propogate to tabs
|
||||
var onRouteChange = _.bindKey(internals.tabs, 'consumeRouteUpdate', $location, chrome.getVisible());
|
||||
var onRouteChange = function () {
|
||||
let { href } = window.location;
|
||||
let persist = chrome.getVisible();
|
||||
internals.trackPossibleSubUrl(href);
|
||||
internals.tabs.consumeRouteUpdate(href, persist);
|
||||
};
|
||||
|
||||
$rootScope.$on('$routeChangeSuccess', onRouteChange);
|
||||
$rootScope.$on('$routeUpdate', onRouteChange);
|
||||
onRouteChange();
|
||||
|
@ -52,6 +62,9 @@ module.exports = function (chrome, internals) {
|
|||
// and some local values
|
||||
$scope.httpActive = $http.pendingRequests;
|
||||
$scope.notifList = require('ui/notify')._notifs;
|
||||
$scope.appSwitcherTemplate = new ConfigTemplate({
|
||||
switcher: '<app-switcher></app-switcher>'
|
||||
});
|
||||
|
||||
return chrome;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
var _ = require('lodash');
|
||||
const { clone, get } = require('lodash');
|
||||
const { resolve } = require('url');
|
||||
|
||||
module.exports = function (chrome, internals) {
|
||||
|
||||
if (internals.app) {
|
||||
internals.app.url = resolve(window.location.href, internals.app.url);
|
||||
}
|
||||
|
||||
internals.appUrlStore = internals.appUrlStore || window.sessionStorage;
|
||||
|
||||
/**
|
||||
* ui/chrome apps API
|
||||
*
|
||||
|
@ -16,24 +23,33 @@ module.exports = function (chrome, internals) {
|
|||
};
|
||||
|
||||
chrome.getShowAppsLink = function () {
|
||||
return internals.showAppsLink == null ? internals.appCount > 1 : internals.showAppsLink;
|
||||
return internals.showAppsLink == null ? internals.nav.length > 1 : internals.showAppsLink;
|
||||
};
|
||||
|
||||
chrome.getApp = function () {
|
||||
return _.clone(internals.app);
|
||||
return clone(internals.app);
|
||||
};
|
||||
|
||||
chrome.getAppTitle = function () {
|
||||
return internals.app.title;
|
||||
return get(internals, ['app', 'title']);
|
||||
};
|
||||
|
||||
chrome.getAppId = function () {
|
||||
return internals.app.id;
|
||||
chrome.getAppUrl = function () {
|
||||
return get(internals, ['app', 'url']);
|
||||
};
|
||||
|
||||
chrome.getInjected = function (name, def) {
|
||||
if (name == null) return _.clone(internals.vars) || {};
|
||||
return _.get(internals.vars, name, def);
|
||||
if (name == null) return clone(internals.vars) || {};
|
||||
return get(internals.vars, name, def);
|
||||
};
|
||||
|
||||
chrome.getLastUrlFor = function (appId) {
|
||||
return internals.appUrlStore.getItem(`appLastUrl:${appId}`);
|
||||
};
|
||||
|
||||
chrome.setLastUrlFor = function (appId, url) {
|
||||
internals.appUrlStore.setItem(`appLastUrl:${appId}`, url);
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
|
34
src/ui/public/chrome/api/nav.js
Normal file
34
src/ui/public/chrome/api/nav.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
module.exports = function (chrome, internals) {
|
||||
const { startsWith } = require('lodash');
|
||||
|
||||
chrome.getNavLinks = function () {
|
||||
return internals.nav;
|
||||
};
|
||||
|
||||
chrome.getLastSubUrlFor = function (url) {
|
||||
return internals.appUrlStore.getItem(`lastSubUrl:${url}`);
|
||||
};
|
||||
|
||||
internals.trackPossibleSubUrl = function (url) {
|
||||
for (const link of internals.nav) {
|
||||
if (startsWith(url, link.url)) {
|
||||
link.lastSubUrl = url;
|
||||
internals.appUrlStore.setItem(`lastSubUrl:${link.url}`, url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
internals.nav.forEach(link => {
|
||||
// convert all link urls to absolute urls
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.setAttribute('href', link.url);
|
||||
link.url = a.href;
|
||||
link.lastSubUrl = chrome.getLastSubUrlFor(link.url);
|
||||
|
||||
if (link.url === chrome.getAppUrl()) {
|
||||
link.active = true;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
|
@ -1,7 +1,14 @@
|
|||
var _ = require('lodash');
|
||||
var TabCollection = require('../TabCollection');
|
||||
|
||||
module.exports = function (chrome, internals) {
|
||||
|
||||
internals.tabs = new TabCollection({
|
||||
defaults: {
|
||||
baseUrl: `${chrome.getAppUrl()}#/`
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* ui/chrome tabs API
|
||||
*
|
||||
|
@ -26,7 +33,7 @@ module.exports = function (chrome, internals) {
|
|||
* when the the tab is considered active, should clicking it
|
||||
* cause a redirect to just the id?
|
||||
*
|
||||
* trackLastPath {boolean}
|
||||
* trackLastUrl {boolean}
|
||||
* When this tab is active, should the current path be tracked
|
||||
* and persisted to session storage, then used as the tabs href attribute when the user navigates
|
||||
* away from the tab?
|
||||
|
|
|
@ -60,4 +60,41 @@ module.exports = function (chrome, internals) {
|
|||
return internals.brand[item];
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a class to the application node
|
||||
* @param {string} - the class name to add
|
||||
* @return {chrome}
|
||||
*/
|
||||
chrome.addApplicationClass = function (val) {
|
||||
var classes = internals.applicationClasses || [];
|
||||
classes.push(val);
|
||||
classes = _.uniq(classes);
|
||||
|
||||
internals.applicationClasses = classes;
|
||||
return chrome;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a class from the application node. Note: this only
|
||||
* removes classes that were added via the addApplicationClass method
|
||||
* @param {string|[string]} - class or classes to be removed
|
||||
* @return {chrome}
|
||||
*/
|
||||
chrome.removeApplicationClass = function (val) {
|
||||
var classesToRemove = [].concat(val || []);
|
||||
var classes = internals.applicationClasses || [];
|
||||
_.pull(classes, ...classesToRemove);
|
||||
|
||||
internals.applicationClasses = classes;
|
||||
return chrome;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {string} - a space delimited string of the classes added by the
|
||||
* addApplicationClass method
|
||||
*/
|
||||
chrome.getApplicationClasses = function () {
|
||||
return internals.applicationClasses.join(' ');
|
||||
};
|
||||
|
||||
};
|
||||
|
|
221
src/ui/public/chrome/appSwitcher/__tests__/appSwitcher.js
Normal file
221
src/ui/public/chrome/appSwitcher/__tests__/appSwitcher.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
var sinon = require('auto-release-sinon');
|
||||
var ngMock = require('ngMock');
|
||||
var $ = require('jquery');
|
||||
var expect = require('expect.js');
|
||||
var constant = require('lodash').constant;
|
||||
var set = require('lodash').set;
|
||||
var cloneDeep = require('lodash').cloneDeep;
|
||||
var indexBy = require('lodash').indexBy;
|
||||
|
||||
require('ui/chrome');
|
||||
require('ui/chrome/appSwitcher');
|
||||
var DomLocationProvider = require('ui/domLocation');
|
||||
|
||||
describe('appSwitcher directive', function () {
|
||||
var env;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
function setup(href, links) {
|
||||
return ngMock.inject(function ($window, $rootScope, $compile, Private) {
|
||||
var domLocation = Private(DomLocationProvider);
|
||||
|
||||
$rootScope.chrome = {
|
||||
getNavLinks: constant(cloneDeep(links)),
|
||||
};
|
||||
|
||||
env = {
|
||||
$scope: $rootScope,
|
||||
$el: $compile($('<app-switcher>'))($rootScope),
|
||||
currentHref: href,
|
||||
location: domLocation
|
||||
};
|
||||
|
||||
Object.defineProperties(domLocation, {
|
||||
href: {
|
||||
get: function () { return env.currentHref; },
|
||||
set: function (val) { return env.currentHref = val; },
|
||||
},
|
||||
reload: {
|
||||
value: sinon.stub()
|
||||
}
|
||||
});
|
||||
|
||||
env.$scope.$digest();
|
||||
});
|
||||
}
|
||||
|
||||
context('when one link is for the active app', function () {
|
||||
var myLink = {
|
||||
active: true,
|
||||
title: 'myLink',
|
||||
url: 'http://localhost:555/app/myApp',
|
||||
lastSubUrl: 'http://localhost:555/app/myApp#/lastSubUrl'
|
||||
};
|
||||
|
||||
var notMyLink = {
|
||||
active: false,
|
||||
title: 'notMyLink',
|
||||
url: 'http://localhost:555/app/notMyApp',
|
||||
lastSubUrl: 'http://localhost:555/app/notMyApp#/lastSubUrl'
|
||||
};
|
||||
|
||||
beforeEach(setup('http://localhost:5555/app/myApp/', [myLink, notMyLink]));
|
||||
|
||||
it('links to the inactive apps base url', function () {
|
||||
var $myLink = env.$el.findTestSubject('appLink').eq(0);
|
||||
expect($myLink.prop('href')).to.be(myLink.url);
|
||||
expect($myLink.prop('href')).to.not.be(myLink.lastSubUrl);
|
||||
});
|
||||
|
||||
it('links to the inactive apps last sub url', function () {
|
||||
var $notMyLink = env.$el.findTestSubject('appLink').eq(1);
|
||||
expect($notMyLink.prop('href')).to.be(notMyLink.lastSubUrl);
|
||||
expect($notMyLink.prop('href')).to.not.be(notMyLink.url);
|
||||
});
|
||||
});
|
||||
|
||||
context('when none of the links are for the active app', function () {
|
||||
var myLink = {
|
||||
active: false,
|
||||
title: 'myLink',
|
||||
url: 'http://localhost:555/app/myApp',
|
||||
lastSubUrl: 'http://localhost:555/app/myApp#/lastSubUrl'
|
||||
};
|
||||
|
||||
var notMyLink = {
|
||||
active: false,
|
||||
title: 'notMyLink',
|
||||
url: 'http://localhost:555/app/notMyApp',
|
||||
lastSubUrl: 'http://localhost:555/app/notMyApp#/lastSubUrl'
|
||||
};
|
||||
|
||||
beforeEach(setup('http://localhost:5555/app/myApp/', [myLink, notMyLink]));
|
||||
|
||||
it('links to the lastSubUrl for each', function () {
|
||||
var $links = env.$el.findTestSubject('appLink');
|
||||
var $myLink = $links.eq(0);
|
||||
var $notMyLink = $links.eq(1);
|
||||
|
||||
expect($myLink.prop('href')).to.be(myLink.lastSubUrl);
|
||||
expect($myLink.prop('href')).to.not.be(myLink.url);
|
||||
|
||||
expect($notMyLink.prop('href')).to.be(notMyLink.lastSubUrl);
|
||||
expect($notMyLink.prop('href')).to.not.be(notMyLink.url);
|
||||
});
|
||||
});
|
||||
|
||||
context('clicking a link with matching href but missing hash', function () {
|
||||
var url = 'http://localhost:555/app/myApp?query=1';
|
||||
beforeEach(setup(url + '#/lastSubUrl', [
|
||||
{ url: url }
|
||||
]));
|
||||
|
||||
it('just prevents propogation (no reload)', function () {
|
||||
var event = new $.Event('click');
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isDefaultPrevented()).to.be(false);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
|
||||
var $link = env.$el.findTestSubject('appLink');
|
||||
expect($link.prop('href')).to.be(url);
|
||||
$link.trigger(event);
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isDefaultPrevented()).to.be(false);
|
||||
expect(event.isPropagationStopped()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
context('clicking a link that matches entire url', function () {
|
||||
var url = 'http://localhost:555/app/myApp#/lastSubUrl';
|
||||
beforeEach(setup(url, [
|
||||
{ url: url }
|
||||
]));
|
||||
|
||||
it('calls window.location.reload and prevents propogation', function () {
|
||||
var event = new $.Event('click');
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isDefaultPrevented()).to.be(false);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
|
||||
var $link = env.$el.findTestSubject('appLink');
|
||||
expect($link.prop('href')).to.be(env.currentHref);
|
||||
$link.trigger(event);
|
||||
|
||||
expect(env.location.reload.callCount).to.be(1);
|
||||
expect(event.isDefaultPrevented()).to.be(false);
|
||||
expect(event.isPropagationStopped()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
context('clicking a link with matching href but changed hash', function () {
|
||||
var rootUrl = 'http://localhost:555/app/myApp?query=1';
|
||||
var url = rootUrl + '#/lastSubUrl2';
|
||||
|
||||
beforeEach(setup(url + '#/lastSubUrl', [
|
||||
{ url: url }
|
||||
]));
|
||||
|
||||
it('calls window.location.reload and prevents propogation', function () {
|
||||
var event = new $.Event('click');
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isDefaultPrevented()).to.be(false);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
|
||||
var $link = env.$el.findTestSubject('appLink');
|
||||
expect($link.prop('href')).to.be(url);
|
||||
$link.trigger(event);
|
||||
|
||||
expect(env.location.reload.callCount).to.be(1);
|
||||
expect(event.isDefaultPrevented()).to.be(false);
|
||||
expect(event.isPropagationStopped()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
context('clicking a link with matching host', function () {
|
||||
beforeEach(setup('http://localhost:555/someOtherPath', [
|
||||
{
|
||||
active: true,
|
||||
url: 'http://localhost:555/app/myApp'
|
||||
}
|
||||
]));
|
||||
|
||||
it('allows click through', function () {
|
||||
var event = new $.Event('click');
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
|
||||
env.$el.findTestSubject('appLink').trigger(event);
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('clicking a link with matching host and path', function () {
|
||||
beforeEach(setup('http://localhost:555/app/myApp?someQuery=true', [
|
||||
{
|
||||
active: true,
|
||||
url: 'http://localhost:555/app/myApp?differentQuery=true'
|
||||
}
|
||||
]));
|
||||
|
||||
it('allows click through', function () {
|
||||
var event = new $.Event('click');
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
|
||||
env.$el.findTestSubject('appLink').trigger(event);
|
||||
|
||||
expect(env.location.reload.callCount).to.be(0);
|
||||
expect(event.isPropagationStopped()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
18
src/ui/public/chrome/appSwitcher/appSwitcher.html
Normal file
18
src/ui/public/chrome/appSwitcher/appSwitcher.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="app-links">
|
||||
<div
|
||||
class="app-link"
|
||||
ng-repeat="link in switcher.getNavLinks() | orderBy:'title'"
|
||||
ng-class="{ active: link.active }">
|
||||
|
||||
<a
|
||||
ng-click="switcher.ensureNavigation($event, link)"
|
||||
ng-href="{{ link.active ? link.url : (link.lastSubUrl || link.url) }}"
|
||||
data-test-subj="appLink">
|
||||
|
||||
<div ng-if="link.icon" ng-style="{ 'background-image': 'url(../' + link.icon + ')' }" class="app-icon"></div>
|
||||
<div ng-if="!link.icon" class="app-icon app-icon-missing">{{ link.title[0] }}</div>
|
||||
|
||||
<div class="app-title">{{ link.title }}</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
51
src/ui/public/chrome/appSwitcher/appSwitcher.js
Normal file
51
src/ui/public/chrome/appSwitcher/appSwitcher.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
var parse = require('url').parse;
|
||||
var bindKey = require('lodash').bindKey;
|
||||
|
||||
require('../appSwitcher/appSwitcher.less');
|
||||
var DomLocationProvider = require('ui/domLocation');
|
||||
|
||||
require('ui/modules')
|
||||
.get('kibana')
|
||||
.directive('appSwitcher', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: require('./appSwitcher.html'),
|
||||
controllerAs: 'switcher',
|
||||
controller: function ($scope, Private) {
|
||||
var domLocation = Private(DomLocationProvider);
|
||||
|
||||
// since we render this in an isolate scope we can't "require: ^chrome", but
|
||||
// rather than remove all helpfull checks we can just check here.
|
||||
if (!$scope.chrome || !$scope.chrome.getNavLinks) {
|
||||
throw new TypeError('appSwitcher directive requires the "chrome" config-object');
|
||||
}
|
||||
|
||||
this.getNavLinks = bindKey($scope.chrome, 'getNavLinks');
|
||||
|
||||
// links don't cause full-navigation events in certain scenarios
|
||||
// so we force them when needed
|
||||
this.ensureNavigation = function (event, app) {
|
||||
if (event.isDefaultPrevented() || event.altKey || event.metaKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
var toParsed = parse(event.delegateTarget.href);
|
||||
var fromParsed = parse(domLocation.href);
|
||||
var sameProto = toParsed.protocol === fromParsed.protocol;
|
||||
var sameHost = toParsed.host === fromParsed.host;
|
||||
var samePath = toParsed.path === fromParsed.path;
|
||||
|
||||
if (sameProto && sameHost && samePath) {
|
||||
toParsed.hash && domLocation.reload();
|
||||
|
||||
// event.preventDefault() keeps the browser from seeing the new url as an update
|
||||
// and even setting window.location does not mimic that behavior, so instead
|
||||
// we use stopPropagation() to prevent angular from seeing the click and
|
||||
// starting a digest cycle/attempting to handle it in the router.
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
});
|
58
src/ui/public/chrome/appSwitcher/appSwitcher.less
Normal file
58
src/ui/public/chrome/appSwitcher/appSwitcher.less
Normal file
|
@ -0,0 +1,58 @@
|
|||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
@app-icon-size: 48px;
|
||||
@app-icon-padding: 10px;
|
||||
|
||||
.app-links {
|
||||
text-align: justify;
|
||||
|
||||
.app-link {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
width: @app-icon-size + (@app-icon-padding * 2);
|
||||
margin: 0px 10px;
|
||||
padding: @app-icon-padding;
|
||||
border-radius: @border-radius-base;
|
||||
|
||||
.app-icon {
|
||||
display: block;
|
||||
height: @app-icon-size;
|
||||
width: @app-icon-size;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
border-radius: @border-radius-base;
|
||||
background-color: @gray-light;
|
||||
width: 100%;
|
||||
|
||||
&-missing {
|
||||
text-align: center;
|
||||
font-size: 2.7em;
|
||||
font-weight: bold;
|
||||
font-family: @font-family-sans-serif;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.app-title {
|
||||
color: @text-color;
|
||||
font-size: 0.9em;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
&:hover .app-title {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: @gray-lighter;
|
||||
.app-title {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -41,15 +41,17 @@
|
|||
|
||||
<li ng-if="chrome.getBrand('title')" class="navbar-brand">{{ chrome.getBrand('title') }}</li>
|
||||
|
||||
<li ng-if="chrome.getShowAppsLink()">
|
||||
<a href="/apps"><i class="fa fa-th" alt="Go to app switcher"></i></a>
|
||||
</li>
|
||||
|
||||
<li ng-repeat="tab in chrome.getTabs()" ng-class="{ active: tab.active }">
|
||||
<a ng-href="{{ tab.href() }}" ng-style="{ 'border-bottom-color': tab.activeIndicatorColor }">
|
||||
{{ tab.title }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="to-body" ng-class="{ active: appSwitcherTemplate.is('switcher') }" ng-if="chrome.getShowAppsLink()">
|
||||
<a ng-click="appSwitcherTemplate.toggle('switcher')">
|
||||
<i class="fa fa-th" alt="Show app switcher"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul ng-show="timefilter.enabled" class="nav navbar-nav navbar-right navbar-timepicker">
|
||||
|
@ -95,6 +97,12 @@
|
|||
<!-- /Full navbar -->
|
||||
</nav>
|
||||
|
||||
<config
|
||||
config-template="appSwitcherTemplate"
|
||||
config-object="chrome"
|
||||
config-close="appSwitcherTemplate.close">
|
||||
</config>
|
||||
|
||||
<config
|
||||
ng-show="timefilter.enabled"
|
||||
config-template="pickerTemplate"
|
||||
|
@ -102,5 +110,5 @@
|
|||
config-close="pickerTemplate.close">
|
||||
</config>
|
||||
|
||||
<div class="application" ng-class="'tab-' + chrome.getActiveTabId('-none-')" ng-view></div>
|
||||
<div class="application" ng-class="'tab-' + chrome.getActiveTabId('-none-') + ' ' + chrome.getApplicationClasses()" ng-view></div>
|
||||
</div>
|
||||
|
|
|
@ -8,22 +8,19 @@ require('ui/timefilter');
|
|||
require('ui/private');
|
||||
require('ui/promises');
|
||||
|
||||
var metadata = require('ui/metadata');
|
||||
var TabCollection = require('ui/chrome/TabCollection');
|
||||
|
||||
var chrome = {
|
||||
navBackground: '#222222',
|
||||
logo: null,
|
||||
smallLogo: null
|
||||
};
|
||||
|
||||
var internals = _.assign(
|
||||
_.cloneDeep(window.__KBN__ || {}),
|
||||
var chrome = {};
|
||||
var internals = _.defaults(
|
||||
_.cloneDeep(metadata),
|
||||
{
|
||||
tabs: new TabCollection(),
|
||||
rootController: null,
|
||||
rootTemplate: null,
|
||||
showAppsLink: null,
|
||||
brand: null
|
||||
brand: null,
|
||||
nav: [],
|
||||
applicationClasses: []
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -33,6 +30,7 @@ $('<link>').attr({
|
|||
}).appendTo('head');
|
||||
|
||||
require('./api/apps')(chrome, internals);
|
||||
require('./api/nav')(chrome, internals);
|
||||
require('./api/angular')(chrome, internals);
|
||||
require('./api/controls')(chrome, internals);
|
||||
require('./api/tabs')(chrome, internals);
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('auto-release-sinon');
|
||||
var expect = require('expect.js');
|
||||
var $ = require('jquery');
|
||||
var ngMock = require('ngMock');
|
||||
|
||||
require('ui/clipboard');
|
||||
|
||||
describe('Clipboard directive', function () {
|
||||
var $scope;
|
||||
var $rootScope;
|
||||
var $compile;
|
||||
var $interpolate;
|
||||
var el;
|
||||
var tips;
|
||||
|
||||
function init() {
|
||||
// load the application
|
||||
ngMock.module('kibana');
|
||||
|
||||
ngMock.inject(function (_$rootScope_, _$compile_, _$interpolate_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
$interpolate = _$interpolate_;
|
||||
$rootScope.text = 'foo';
|
||||
|
||||
el = $compile('<kbn-clipboard copy="text"></kbn-clipboard>')($rootScope);
|
||||
|
||||
$scope = el.scope();
|
||||
$scope.$digest();
|
||||
});
|
||||
}
|
||||
|
||||
describe.skip('With flash disabled', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(window.ZeroClipboard, 'isFlashUnusable', _.constant(true));
|
||||
init();
|
||||
});
|
||||
|
||||
it('should be an empty element', function () {
|
||||
expect(el.children()).to.have.length(0);
|
||||
});
|
||||
|
||||
it('should not show the tooltip', function () {
|
||||
var clip = el.find('[tooltip]');
|
||||
expect(clip).to.have.length(0);
|
||||
});
|
||||
|
||||
it('should not show the clipboard button', function () {
|
||||
var clip = el.find('[clip-copy]');
|
||||
expect(clip).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('With flash enabled', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(window.ZeroClipboard, 'isFlashUnusable', _.constant(false));
|
||||
init();
|
||||
});
|
||||
|
||||
it('should contain an element with clip-copy', function () {
|
||||
var clip = el.find('[clip-copy]');
|
||||
expect(clip).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should have a tooltip', function () {
|
||||
var clip = el.find('[tooltip]');
|
||||
expect(clip).to.have.length(1);
|
||||
|
||||
var clipText = $interpolate($(clip).attr('tooltip'))();
|
||||
expect(clipText).to.be('Copy to clipboard');
|
||||
|
||||
});
|
||||
|
||||
it('should change the tooltip text when clicked, back when mouse leaves', function () {
|
||||
el.mouseenter();
|
||||
el.click();
|
||||
$scope.$digest();
|
||||
|
||||
var clipText = $interpolate($('[tooltip]', el).attr('tooltip'))();
|
||||
expect(clipText).to.be('Copied!');
|
||||
|
||||
el.mouseleave();
|
||||
$scope.$digest();
|
||||
|
||||
clipText = $interpolate($('[tooltip]', el).attr('tooltip'))();
|
||||
expect(clipText).to.be('Copy to clipboard');
|
||||
});
|
||||
|
||||
it('should unbind all handlers on destroy', function () {
|
||||
var handlers = $._data(el.get(0), 'events');
|
||||
expect(Object.keys(handlers)).to.have.length(2);
|
||||
|
||||
$scope.$destroy();
|
||||
expect(Object.keys(handlers)).to.have.length(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
<span>
|
||||
<a
|
||||
ng-if="!disabled"
|
||||
tooltip="{{shownText}}"
|
||||
tooltip-placement="{{tipPlacement}}"
|
||||
tooltip-animation="false"
|
||||
tooltip-popup-delay="0"
|
||||
tooltip-append-to-body="true">
|
||||
<i class="fa" ng-class="icon" clip-copy="copyText"></i>
|
||||
</a>
|
||||
</span>
|
|
@ -1,47 +0,0 @@
|
|||
define(function (require) {
|
||||
var ZeroClipboard = require('ng-clip');
|
||||
var $ = require('jquery');
|
||||
var html = require('ui/clipboard/clipboard.html');
|
||||
|
||||
require('ui/modules')
|
||||
.get('kibana')
|
||||
.directive('kbnClipboard', function ($compile, $timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: html,
|
||||
replace: true,
|
||||
scope: {
|
||||
copyText: '=copy'
|
||||
},
|
||||
transclude: true,
|
||||
link: function ($scope, $el, attr) {
|
||||
if (ZeroClipboard.isFlashUnusable()) {
|
||||
$scope.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.tipPlacement = attr.tipPlacement || 'top';
|
||||
$scope.tipText = attr.tipText || 'Copy to clipboard';
|
||||
$scope.tipConfirm = attr.tipConfirm = 'Copied!';
|
||||
$scope.icon = attr.icon || 'fa-clipboard';
|
||||
|
||||
$scope.shownText = $scope.tipText;
|
||||
|
||||
$el.on('click', function () {
|
||||
$scope.shownText = $scope.tipConfirm;
|
||||
// Reposition tooltip to account for text length change
|
||||
$('a', $el).mouseenter();
|
||||
});
|
||||
|
||||
$el.on('mouseleave', function () {
|
||||
$scope.shownText = $scope.tipText;
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$el.off('click');
|
||||
$el.off('mouseleave');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -1,28 +1,33 @@
|
|||
.sidebar-container.collapsible-sidebar {
|
||||
@import (reference) "~ui/styles/variables";
|
||||
|
||||
.collapsible-sidebar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.sidebar-collapser {
|
||||
background-color: #ecf0f1;
|
||||
height: 28px;
|
||||
background-color: @collapser-bg;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -20px;
|
||||
width: 20px;
|
||||
border: 1px solid #ecf0f1;
|
||||
border-width: 0 1px 1px 0;
|
||||
right: -(@collapser-width);
|
||||
width: @collapser-width;
|
||||
cursor: pointer;
|
||||
z-index: -1;
|
||||
border-bottom-right-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: @collapser-hover-bg;
|
||||
border-color: @collapser-hover-bg;
|
||||
color: @collapser-hover-color;
|
||||
}
|
||||
|
||||
.chevron-cont{
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 5px;
|
||||
left: 2px;
|
||||
top: 8px;
|
||||
font-size: 10px;
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
color: #333;
|
||||
content: "\F053";
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +52,7 @@
|
|||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.sidebar-container.collapsible-sidebar {
|
||||
.collapsible-sidebar {
|
||||
&.closed {
|
||||
display: none;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue