├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── comment-on-asciidoc-changes.yml ├── .gitignore ├── .node-version ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── api_server ├── api.js ├── es_1_0.js ├── es_1_0 │ ├── aggregations.js │ ├── aliases.js │ ├── cat.js │ ├── cluster.js │ ├── count.js │ ├── document.js │ ├── facets.js │ ├── filter.js │ ├── globals.js │ ├── indices.js │ ├── mappings.js │ ├── nodes.js │ ├── percolator.js │ ├── query.js │ ├── search.js │ ├── settings.js │ ├── snapshot_restore.js │ ├── templates.js │ └── warmers.js ├── es_2_0.js ├── es_2_0 │ ├── aggregations.js │ ├── aliases.js │ ├── cat.js │ ├── cluster.js │ ├── count.js │ ├── document.js │ ├── field_stats.js │ ├── filter.js │ ├── globals.js │ ├── indices.js │ ├── mappings.js │ ├── nodes.js │ ├── percolator.js │ ├── query.js │ ├── search.js │ ├── settings.js │ ├── snapshot_restore.js │ ├── templates.js │ └── warmers.js └── server.js ├── docs ├── images │ ├── auto_format_after.png │ ├── auto_format_before.png │ ├── auto_format_bulk.png │ ├── history.png │ ├── introduction_action_menu.png │ ├── introduction_output.png │ ├── introduction_screen.png │ ├── introduction_server.png │ ├── introduction_suggestion.png │ ├── multiple_requests.png │ ├── readme_api_suggestions.png │ ├── readme_auto_formatting_mix.png │ ├── readme_copy_as_curl.png │ ├── readme_errors.png │ ├── readme_multiple_requests.png │ ├── readme_scope_collapsing.png │ └── settings.png ├── index.asciidoc ├── installing_sense.asciidoc ├── introduction.asciidoc ├── release_notes.asciidoc └── sense_ui.asciidoc ├── index.js ├── package.json ├── public ├── bonsai.png ├── css │ ├── sense.less │ └── sense.light.css ├── favicon.ico ├── icon.png ├── index.html ├── sense.js ├── src │ ├── app.js │ ├── autocomplete.js │ ├── autocomplete │ │ ├── body_completer.js │ │ ├── engine.js │ │ ├── url_params.js │ │ └── url_pattern_matcher.js │ ├── controllers │ │ └── SenseController.js │ ├── curl.js │ ├── directives │ │ ├── help.html │ │ ├── helpExample.txt │ │ ├── history.html │ │ ├── navbar.html │ │ ├── senseHelp.js │ │ ├── senseHelpExample.js │ │ ├── senseHistory.js │ │ ├── senseHistoryViewer.js │ │ ├── senseNavbar.js │ │ ├── senseSettings.js │ │ ├── senseWelcome.js │ │ ├── settings.html │ │ └── welcome.html │ ├── es.js │ ├── history.js │ ├── input.js │ ├── input_resize.js │ ├── kb.js │ ├── kb │ │ └── api.js │ ├── mappings.js │ ├── output.js │ ├── sense_editor │ │ ├── editor.js │ │ ├── mode │ │ │ ├── input.js │ │ │ ├── input_highlight_rules.js │ │ │ ├── output.js │ │ │ ├── output_highlight_rules.js │ │ │ └── worker.js │ │ ├── row_parser.js │ │ └── theme-sense-dark.js │ ├── settings.js │ ├── smart_resize.js │ ├── storage.js │ └── utils.js ├── tests │ ├── index.html │ ├── src │ │ ├── curl_parsing_tests.js │ │ ├── curl_parsing_tests.txt │ │ ├── editor_input1.txt │ │ ├── editor_tests.js │ │ ├── integration_tests.js │ │ ├── kb_tests.js │ │ ├── mapping_tests.js │ │ ├── tokenization_tests.js │ │ ├── url_autocomplete_tests.js │ │ └── url_params_tests.js │ ├── tests.js │ └── webpackShims │ │ ├── qunit-1.10.0.css │ │ └── qunit-1.10.0.js └── webpackShims │ ├── ace.js │ ├── ace │ ├── ace.js │ ├── ext-language_tools.js │ ├── ext-searchbox.js │ ├── mode-json.js │ ├── mode-yaml.js │ └── worker-json.js │ ├── acequire.js │ ├── ui-bootstrap-custom │ └── ui-bootstrap-custom.js │ └── zeroclip │ ├── zero_clipboard.js │ ├── zero_clipboard.swf │ └── zeroclip.js ├── server ├── __tests__ │ ├── proxy_config.js │ ├── proxy_config_collection.js │ └── wildcard_matcher.js ├── proxy_config.js ├── proxy_config_collection.js └── wildcard_matcher.js └── tasks ├── build.js ├── release.js └── setup_kibana.js /.eslintignore: -------------------------------------------------------------------------------- 1 | webpackShims 2 | public/src/sense_editor/mode/worker.js 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | root: true 4 | extends: airbnb 5 | 6 | env: 7 | node: true 8 | amd: true 9 | 10 | rules: 11 | no-undef: [1] 12 | 13 | func-names: [0] 14 | quotes: [0] 15 | space-before-function-paren: [0] 16 | vars-on-top: [0] 17 | no-cond-assign: [0] 18 | no-param-reassign: [0] 19 | comma-dangle: [0] 20 | no-var: [0] 21 | one-var: [0] 22 | id-length: [0] 23 | eol-last: [0] 24 | consistent-return: [0] 25 | no-use-before-define: [0] 26 | no-nested-ternary: [0] 27 | new-cap: [0] 28 | wrap-iife: [0] 29 | eqeqeq: [0] 30 | no-else-return: [0] 31 | indent: [0] 32 | spaced-comment: [0] 33 | key-spacing: [0] 34 | space-infix-ops: [0] 35 | prefer-const: [0] 36 | camelcase: [0] 37 | no-unused-vars: [0] 38 | padded-blocks: [0] 39 | no-shadow: [0] 40 | semi: [0] 41 | no-console: [0] 42 | brace-style: [0] 43 | no-empty: [0] 44 | strict: [0] 45 | no-redeclare: [0] 46 | curly: [0] 47 | default-case: [0] 48 | dot-notation: [0] 49 | no-multi-spaces: [0] 50 | guard-for-in: [0] 51 | yoda: [0] 52 | no-new: [0] 53 | no-throw-literal: [0] 54 | no-unreachable: [0] 55 | no-multi-str: [0] 56 | -------------------------------------------------------------------------------- /.github/workflows/comment-on-asciidoc-changes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Comment on PR for .asciidoc changes 3 | 4 | on: 5 | # We need to use pull_request_target to be able to comment on PRs from forks 6 | pull_request_target: 7 | types: 8 | - synchronize 9 | - opened 10 | - reopened 11 | branches: 12 | - main 13 | - master 14 | - "9.0" 15 | 16 | jobs: 17 | comment-on-asciidoc-change: 18 | permissions: 19 | contents: read 20 | pull-requests: write 21 | uses: elastic/docs-builder/.github/workflows/comment-on-asciidoc-changes.yml@main 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | *.iml 4 | node_modules 5 | build 6 | .aws-config.json 7 | html_docs 8 | target 9 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 4.3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 4 3 | env: 4 | - CXX=g++-4.8 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | 12 | install: 13 | - npm install 14 | - npm run setup_kibana 15 | 16 | cache: 17 | directories: 18 | - node_modules 19 | - ../kibana 20 | 21 | script: npm test 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Sense 2 | ============================= 3 | 4 | Sense is an open source project and we love to receive contributions from our community — you! 5 | 6 | Contributing to the project can be done in many ways - by taking the time to write a concise bug report, suggesting 7 | a new feature or writing code which can then be offered via a PR. 8 | 9 | Bug reports 10 | ----------- 11 | 12 | If you think you have found a bug, please take a few minutes to collect some information: 13 | 14 | 1. What Sense & Kibana versions are you using? 15 | 2. What Elasticsearch version was Sense pointing at? 16 | 3. Sense pulls information like mapping and indexes and incorporates it into its suggestions. 17 | Please gist and link any information that is relevant to reproduce the bug. 18 | 19 | Contributing code 20 | ----------------- 21 | 22 | If you have a bugfix or new feature that you would like to contribute to Sense, please find or open 23 | an issue about it first. Talk about what you would like to do. It may be that somebody is already working on it, 24 | or that there are particular issues that you should know about before implementing the change. 25 | 26 | When you are ready to start coding, here are a set of steps to follow: 27 | 28 | ### Fork the Sense repository 29 | 30 | You will need to fork the main Sense repository and clone it to your local machine. See 31 | [github help page](https://help.github.com/articles/fork-a-repo) for help. 32 | 33 | ### Setup a Kibana development environment 34 | 35 | Sense is a Kibana app. Production Kibana is geared towards performance will pre-optimize and cache code. 36 | Setting up the development environment will change this behavior and reload your changes as you make them. 37 | 38 | Follow the instruction described on the [Kibana Contributing Guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#development-environment-setup). 39 | 40 | 41 | ### Setup a Sense development environment 42 | 43 | Once Kibana is setup, make sure your have the 4.2 branch checkout: 44 | 45 | ```sh 46 | git checkout 4.2 47 | ``` 48 | 49 | Next, clone your Sense fork into it's `installedPlugin` folder: 50 | 51 | ```sh 52 | git clone git@github.com:YOUR_GITHUB_HANDLE/sense.git installedPlugins/sense 53 | ``` 54 | 55 | Create a file named `config/kibana.dev.yml` (under the Kibana *root folder*), with the following content: 56 | 57 | ```yaml 58 | kibana.enabled: false # do not load the Discover, Visualize and Dashboard tabs of Kibana. This saves time 59 | elasticsearch.enabled: false # do not require an active Elasticsearch instance to run 60 | optimize: 61 | sourceMaps: '#cheap-module-source-map' 62 | unsafeCache: true 63 | lazyPrebuild: false 64 | ``` 65 | 66 | last, run Kibana in development mode: 67 | 68 | ```sh 69 | ./bin/kibana --dev 70 | ``` 71 | 72 | Congratulations, you should have Sense up and running now. 73 | You can check that by pointing your browser at http://localhost:5601/app/sense/ 74 | 75 | ### Linting your code 76 | 77 | Sense uses the fantastic [eslint](http://eslint.org) tool to detect common programming errors and to enforce a uniform code style. To check your code with the linter simply run the lint script in your terminal: 78 | 79 | ```sh 80 | npm run lint 81 | ``` 82 | 83 | Eslint also has plugins for most text editors and IDEs so that you can get linting results as you work. Here are some hints for getting eslint setup in your favorite editor: 84 | 85 | | Editor | Plugin | 86 | | --- | --- | --- | 87 | | Sublime | [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint#installation) | 88 | | Atom | [linter-eslint](https://github.com/AtomLinter/linter-eslint#installation) | 89 | | IntelliJ | Settings » Languages & Frameworks » JavaScript » Code Quality Tools » ESLint | 90 | | vi | [scrooloose/syntastic](https://github.com/scrooloose/syntastic) | 91 | 92 | ### Submitting your changes 93 | 94 | Once your changes and tests are ready to submit for review: 95 | 96 | 1. Test your changes 97 | 98 | 2. Sign the Contributor License Agreement 99 | 100 | Please make sure you have signed our [Contributor License Agreement](https://www.elastic.co/contributor-agreement/). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once. 101 | 102 | 3. Rebase your changes 103 | 104 | Update your local repository with the most recent code from the main Sense repository, 105 | and rebase your branch on top of the latest master branch. We prefer your initial changes to be squashed 106 | into a single commit. Later, if we ask you to make changes, add them as separate commits. 107 | This makes them easier to review. 108 | As a final step before merging we will either ask you to squash all commits yourself or we'll do it for you. 109 | 110 | 4. Submit a pull request 111 | 112 | Push your local changes to your forked copy of the repository and [submit a pull request](https://help.github.com/articles/using-pull-requests). 113 | In the pull request, choose a title which sums up the changes that you have made, and in the body provide 114 | more details about what your changes do. Also mention the number of the issue where discussion has taken 115 | place, eg "Closes #123". 116 | 117 | Then sit back and wait. There will probably be discussion about the pull request and, if any changes are needed, 118 | we would love to work with you to get your pull request merged into Sense. 119 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | require('jit-grunt')(grunt, { 3 | s3: 'grunt-aws', 4 | eslint: 'gruntify-eslint' 5 | }); 6 | 7 | const srcFiles = [ 8 | { 9 | expand: true, 10 | src: [ 11 | 'public/**', 12 | 'server/**', 13 | '!public/tests/**', 14 | 'api_server/**', 15 | 'index.js', 16 | 'LICENSE.md', 17 | 'package.json', 18 | 'README.md', 19 | ], 20 | dest: 'build/sense-<%= pkg.version %>' 21 | } 22 | ]; 23 | 24 | const replaceSrcFiles = [ 25 | { 26 | expand: true, 27 | src: [ 28 | 'public/**/*.js', 29 | 'public/**/*.css', 30 | '!public/tests/**', 31 | 'api_server/**', 32 | 'index.js', 33 | 'LICENSE.md', 34 | 'package.json', 35 | 'README.md', 36 | ], 37 | dest: 'build/sense-<%= pkg.version %>' 38 | } 39 | ]; 40 | 41 | 42 | grunt.initConfig({ 43 | pkg: require('./package.json'), 44 | 45 | clean: { 46 | build: { src: 'build' }, 47 | target: { src: 'target' }, 48 | }, 49 | 50 | compress: { 51 | build: { 52 | options: { 53 | mode: 'tgz', 54 | archive: 'target/sense-<%= pkg.version %>.tar.gz' 55 | }, 56 | files: [ 57 | { 58 | expand: true, 59 | cwd: 'build/', 60 | src: ['**'], 61 | dest: '' 62 | }, 63 | ] 64 | } 65 | }, 66 | 67 | copy: { 68 | build: { 69 | files: srcFiles 70 | } 71 | }, 72 | 73 | s3: { 74 | release: { 75 | options: { 76 | bucket: 'download.elasticsearch.org', 77 | access: 'private', 78 | gzip: false 79 | }, 80 | files: [ 81 | { 82 | src: 'target/sense-<%= pkg.version %>.tar.gz', 83 | dest: 'elasticsearch/sense/sense-<%= pkg.version %>.tar.gz' 84 | } 85 | ] 86 | } 87 | }, 88 | 89 | eslint: { 90 | source: { 91 | src: [ 92 | 'public/**/*.js', 93 | '!**/webpackShims/**' 94 | ] 95 | } 96 | }, 97 | 98 | gitinfo: {}, 99 | 100 | replace: { 101 | build: { 102 | options: { 103 | patterns: [ 104 | { 105 | match: 'SENSE_REVISION', 106 | replacement: '<%= gitinfo.local.branch.current.SHA %>' 107 | } 108 | ] 109 | }, 110 | files: replaceSrcFiles 111 | } 112 | }, 113 | 114 | run: { 115 | npmInstallInBuild: { 116 | cmd: 'npm', 117 | args: [ 118 | 'install', 119 | '--production' 120 | ], 121 | options: { 122 | cwd: 'build/sense-<%= pkg.version %>' 123 | } 124 | } 125 | } 126 | }); 127 | 128 | require('./tasks/build')(grunt); 129 | require('./tasks/release')(grunt); 130 | require('./tasks/setup_kibana')(grunt); 131 | }; 132 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2012–2014 Elasticsearch BV 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HEADS UP: This repo is deprecated. Sense is now included as Console in Kibana 5.0. File issues over at [elastic/kibana](https://www.github.com/elastic/kibana) 2 | 3 | ## Sense 4 | 5 | A JSON aware developer's interface to Elasticsearch. Comes with handy machinery such as syntax highlighting, API suggestions, 6 | formatting and code folding. 7 | 8 | ### Installation 9 | 10 | Sense is a Kibana app. To get up and running you will first need to download Kibana and install as instructed [here](https://www.elastic.co/downloads/kibana). 11 | Once Kibana is installed, you can simply install Sense running the following command from your Kibana folder: 12 | 13 | ``` 14 | $ ./bin/kibana plugin --install elastic/sense 15 | ``` 16 | 17 | Now start your Kibana server by running: 18 | 19 | ``` 20 | $ ./bin/kibana 21 | ``` 22 | 23 | Sense should be available on http://localhost:5601/app/sense (assuming Kibana defaults). 24 | 25 | For more information and advanced setting please see the [documentation](https://www.elastic.co/guide/en/sense/current/installing.html). 26 | 27 | 28 | ### Screenshots 29 | 30 | 31 | #### Handy API suggestions 32 | 33 | Sense offers handy contextual suggestion to the Elasticsearch API. 34 | 35 | 36 | 37 | #### Format validation 38 | 39 | Sometimes it is hard to find that little missing comma. Sense automatically highlights and explains invalid input. 40 | 41 | 42 | 43 | #### Scope collapsing 44 | 45 | Working on a big JSON query can be distracting. Using the mouse or via a handy keyboard shortcut (Ctrl/Cmd+Option+0) 46 | , Sense allows you to focus on a sub section and fold others away. 47 | 48 | 49 | 50 | #### Auto formatting 51 | 52 | Type your commands however you want and let Sense format them for you. 53 | 54 | 55 | 56 | #### Submit multiple requests at once 57 | 58 | When testing or trying things out you frequently need to repeat the same sequence of commands. 59 | Just write them all in Sense, select and submit multiple requests to Elasticsearch. 60 | 61 | 62 | 63 | #### Copy and Paste cURL commands 64 | 65 | Once done, you can copy one or more requests as cURL commands (and of course paste them back) 66 | 67 | 68 | 69 | Results in: 70 | 71 | ``` 72 | # Delete all data in the `website` index 73 | curl -XDELETE "http://localhost:9200/website" 74 | 75 | # Create a document with ID 123 76 | curl -XPUT "http://localhost:9200/website/blog/123" -d' 77 | { 78 | "title": "My first blog entry", 79 | "text": "Just trying this out...", 80 | "date": "2014/01/01" 81 | }' 82 | ``` 83 | 84 | ### Documentation 85 | 86 | Visit [elastic.co](https://www.elastic.co/guide/en/sense/current/index.html) for the full documentation. 87 | 88 | 89 | -------------------------------------------------------------------------------- /api_server/api.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * 7 | * @param name 8 | */ 9 | function Api(name) { 10 | this.globalRules = {}; 11 | this.endpoints = {}; 12 | this.name = name; 13 | } 14 | 15 | (function (cls) { 16 | cls.addGlobalAutocompleteRules = function (parentNode, rules) { 17 | this.globalRules[parentNode] = rules; 18 | }; 19 | 20 | cls.addEndpointDescription = function (endpoint, description) { 21 | if (this.endpoints[endpoint]) { 22 | throw new Error("endpoint [" + endpoint + "] is already registered"); 23 | } 24 | 25 | var copiedDescription = {}; 26 | _.extend(copiedDescription, description || {}); 27 | _.defaults(copiedDescription, { 28 | id: endpoint, 29 | patterns: [endpoint], 30 | methods: ['GET'] 31 | }); 32 | this.endpoints[endpoint] = copiedDescription; 33 | }; 34 | 35 | cls.asJson = function () { 36 | return { 37 | "name": this.name, 38 | "globals": this.globalRules, 39 | "endpoints": this.endpoints 40 | } 41 | }; 42 | 43 | }(Api.prototype)); 44 | 45 | module.exports = Api; 46 | -------------------------------------------------------------------------------- /api_server/es_1_0.js: -------------------------------------------------------------------------------- 1 | let _ = require("lodash"); 2 | let Api = require('./api'); 3 | 4 | let parts = [ 5 | require('./es_1_0/aliases'), 6 | require('./es_1_0/aggregations'), 7 | require('./es_1_0/cat'), 8 | require('./es_1_0/cluster'), 9 | require('./es_1_0/count'), 10 | require('./es_1_0/document'), 11 | require('./es_1_0/facets'), 12 | require('./es_1_0/filter'), 13 | require('./es_1_0/nodes'), 14 | require('./es_1_0/globals'), 15 | require('./es_1_0/indices'), 16 | require('./es_1_0/mappings'), 17 | require('./es_1_0/percolator'), 18 | require('./es_1_0/query'), 19 | require('./es_1_0/snapshot_restore'), 20 | require('./es_1_0/search'), 21 | require('./es_1_0/settings'), 22 | require('./es_1_0/templates'), 23 | require('./es_1_0/warmers') 24 | ]; 25 | 26 | function ES_1_0() { 27 | Api.call(this, "es_1_0"); 28 | _.each(parts, function (apiSection) { 29 | apiSection(this); 30 | }, this); 31 | } 32 | 33 | ES_1_0.prototype = _.create(Api.prototype, {'constructor': ES_1_0}); 34 | 35 | (function (cls) { 36 | cls.addEndpointDescription = function (endpoint, description) { 37 | if (description) { 38 | var url_params_def = {}; 39 | _.each(description.patterns || [], function (p) { 40 | if (p.indexOf("{indices}") >= 0) { 41 | url_params_def["ignore_unavailable"] = "__flag__"; 42 | url_params_def["allow_no_indices"] = "__flag__"; 43 | url_params_def["expand_wildcards"] = ["open", "closed"]; 44 | } 45 | }); 46 | 47 | if (url_params_def) { 48 | description.url_params = description.url_params || {}; 49 | _.defaults(description.url_params, url_params_def); 50 | } 51 | } 52 | Object.getPrototypeOf(cls).addEndpointDescription.call(this, endpoint, description); 53 | }; 54 | })(ES_1_0.prototype); 55 | 56 | module.exports = new ES_1_0(); 57 | -------------------------------------------------------------------------------- /api_server/es_1_0/aliases.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_post_aliases', { 3 | methods: ['POST'], 4 | patterns: [ 5 | "_aliases", 6 | ], 7 | data_autocomplete_rules: { 8 | 'actions': { 9 | __template: [ 10 | {'add': {'index': 'test1', 'alias': 'alias1'}} 11 | ], 12 | __any_of: [ 13 | { 14 | add: { 15 | index: '{index}', 16 | alias: '', 17 | filter: {}, 18 | routing: '1', 19 | search_routing: '1,2', 20 | index_routing: '1' 21 | }, 22 | remove: { 23 | index: '', 24 | alias: '' 25 | } 26 | } 27 | ] 28 | } 29 | } 30 | }); 31 | api.addEndpointDescription('_get_aliases', { 32 | methods: ['GET'], 33 | patterns: [ 34 | "_aliases", 35 | ] 36 | }); 37 | 38 | var aliasRules = { 39 | filter: {}, 40 | routing: '1', 41 | search_routing: '1,2', 42 | index_routing: '1' 43 | }; 44 | 45 | api.addEndpointDescription('_post_alias', { 46 | methods: ["POST", "PUT"], 47 | patterns: [ 48 | "{indices}/_alias/{name}" 49 | ], 50 | data_autocomplete_rules: aliasRules 51 | }); 52 | api.addEndpointDescription('_delete_alias', { 53 | methods: ["DELETE"], 54 | patterns: [ 55 | "{indices}/_alias/{name}" 56 | ] 57 | }); 58 | api.addEndpointDescription('_get_alias', { 59 | methods: ["GET"], 60 | patterns: [ 61 | "_alias", 62 | "{indices}/_alias", 63 | "{indices}/_alias/{name}", 64 | "_alias/{name}" 65 | ] 66 | }); 67 | 68 | api.addGlobalAutocompleteRules('aliases', { 69 | '*': aliasRules 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /api_server/es_1_0/cat.js: -------------------------------------------------------------------------------- 1 | let _ = require("lodash"); 2 | 3 | function addSimpleCat(endpoint, api, params, patterns) { 4 | var url_params = {"help": "__flag__", "v": "__flag__", "bytes": ["b"]}; 5 | _.each(params || [], function (p) { 6 | if (_.isString(p)) { 7 | url_params[p] = "__flag__"; 8 | } 9 | else { 10 | var k = Object.keys(p)[0]; 11 | url_params[k] = p[k]; 12 | } 13 | }); 14 | api.addEndpointDescription(endpoint, { 15 | match: endpoint, 16 | url_params: url_params, 17 | patterns: patterns || [endpoint] 18 | }); 19 | } 20 | 21 | module.exports = function (api) { 22 | addSimpleCat('_cat/aliases', api); 23 | addSimpleCat('_cat/allocation', api, null, ['_cat/allocation', '_cat/allocation/{nodes}']); 24 | addSimpleCat('_cat/count', api); 25 | addSimpleCat('_cat/health', api, [ 26 | {"ts": ["false", "true"]} 27 | ]); 28 | addSimpleCat('_cat/indices', api, [ 29 | {h: []}, 30 | "pri", 31 | ], 32 | ['_cat/indices', '_cat/indices/{indices}']); 33 | addSimpleCat('_cat/master', api); 34 | addSimpleCat('_cat/nodes', api); 35 | addSimpleCat('_cat/pending_tasks', api); 36 | addSimpleCat('_cat/recovery', api); 37 | addSimpleCat('_cat/thread_pool', api); 38 | addSimpleCat('_cat/shards', api); 39 | addSimpleCat('_cat/plugins', api); 40 | addSimpleCat('_cat/segments', api); 41 | }; 42 | -------------------------------------------------------------------------------- /api_server/es_1_0/cluster.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_cluster/nodes/stats'); 3 | api.addEndpointDescription('_cluster/state', { 4 | patterns: [ 5 | "_cluster/state", 6 | "_cluster/state/{metrics}", 7 | "_cluster/state/{metrics}/{indices}" 8 | ], 9 | url_components: { 10 | "metrics": ["version", "master_node", "nodes", "routing_table", "metadata", "blocks"] 11 | } 12 | }); 13 | api.addEndpointDescription('_cluster/health', { 14 | url_params: { 15 | "local": "__flag__", 16 | "level": ["indices", "shards"], 17 | "master_timeout": "30s", 18 | "timeout": "30s", 19 | "wait_for_status": ["yellow", "green"], 20 | "wait_for_relocating_shards": 0, 21 | "wait_for_active_shards": 0, 22 | "wait_for_nodes": 0 23 | } 24 | }); 25 | api.addEndpointDescription('_cluster/pending_tasks'); 26 | api.addEndpointDescription('get_cluster/settings', { 27 | patterns: [ 28 | '_cluster/settings' 29 | ] 30 | }); 31 | 32 | api.addEndpointDescription('put_cluster/settings', { 33 | methods: ['PUT'], 34 | patterns: [ 35 | '_cluster/settings' 36 | ], 37 | data_autocomplete_rules: { 38 | persistent: { 39 | cluster: { 40 | routing: { 41 | 'allocation.enable': {__one_of: ["all", "primaries", "new_primaries", "none"]}, 42 | 'allocation.disk.threshold_enabled': {__one_of: [false, true]}, 43 | 'allocation.disk.watermark.low': '85%', 44 | 'allocation.disk.watermark.high': '90%', 45 | 'allocation.disk.include_relocations': {__one_of: [true, false]}, 46 | 'allocation.disk.reroute_interval': '60s', 47 | 'allocation.exclude': { 48 | '_ip': "", 49 | '_name': "", 50 | '_host': "", 51 | '_id': "" 52 | }, 53 | 'allocation.include': { 54 | '_ip': "", 55 | '_name': "", 56 | '_host': "", 57 | '_id': "" 58 | }, 59 | 'allocation.require': { 60 | '_ip': "", 61 | '_name': "", 62 | '_host': "", 63 | '_id': "" 64 | }, 65 | 'allocation.awareness.attributes': [], 66 | 'allocation.awareness.force': { 67 | '*': { 68 | 'values': [] 69 | } 70 | }, 71 | 'allocation.allow_rebalance': {__one_of: ['always', 'indices_primaries_active', 'indices_all_active']}, 72 | 'allocation.cluster_concurrent_rebalance': 2, 73 | 'allocation.node_initial_primaries_recoveries': 4, 74 | 'allocation.node_concurrent_recoveries': 2, 75 | 'allocation.same_shard.host': {__one_of: [false, true]} 76 | } 77 | }, 78 | indices: { 79 | breaker: { 80 | "total.limit": "70%", 81 | "fielddata.limit": "60%", 82 | "fielddata.overhead": 1.03, 83 | "request.limit": "40%", 84 | "request.overhead": 1.0 85 | } 86 | } 87 | }, 88 | transient: { 89 | __scope_link: '.persistent' 90 | } 91 | } 92 | }); 93 | 94 | api.addEndpointDescription('_cluster/reroute', { 95 | methods: ['POST'], 96 | url_params: { 97 | explain: "__flag__", 98 | dry_run: "__flag__" 99 | }, 100 | data_autocomplete_rules: { 101 | commands: [ 102 | { 103 | move: { 104 | __template: { 105 | index: "", 106 | shard: 0, 107 | from_node: "", 108 | to_node: "" 109 | }, 110 | index: "{index}", 111 | shard: 0, 112 | from_node: "{node}", 113 | to_node: "{node}" 114 | }, 115 | cancel: { 116 | __template: { 117 | index: "", 118 | shard: 0, 119 | node: "" 120 | }, 121 | index: "{index}", 122 | shard: 0, 123 | node: "{node}", 124 | allow_primary: {__one_of: [true, false]} 125 | }, 126 | allocate: { 127 | __template: { 128 | index: "", 129 | shard: 0, 130 | node: "" 131 | }, 132 | index: "{index}", 133 | shard: 0, 134 | node: "{node}", 135 | allow_primary: {__one_of: [true, false]} 136 | } 137 | } 138 | ], 139 | dry_run: {__one_of: [true, false]} 140 | } 141 | }); 142 | }; 143 | -------------------------------------------------------------------------------- /api_server/es_1_0/count.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_count', { 3 | methods: ['GET', 'POST'], 4 | priority: 10, // collides with get doc by id 5 | patterns: [ 6 | "{indices}/{types}/_count", 7 | "{indices}/_count", 8 | "_count" 9 | ], 10 | url_params: { 11 | preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 12 | routing: "", 13 | min_score: 1.0, 14 | terminate_after: 10, 15 | }, 16 | data_autocomplete_rules: { 17 | query: { 18 | // populated by a global rule 19 | }, 20 | } 21 | }) 22 | }; 23 | -------------------------------------------------------------------------------- /api_server/es_1_0/facets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addGlobalAutocompleteRules('facets', { 3 | '*': { 4 | terms: { 5 | __template: { 6 | field: 'FIELD', 7 | size: 10 8 | }, 9 | field: '{field}', 10 | fields: ['{field}'], 11 | size: 10, 12 | script: '', 13 | script_field: '', 14 | order: { 15 | __one_of: ['count', 'term', 'reverse_count', 'reverse_term'] 16 | }, 17 | all_terms: { 18 | __one_of: [false, true] 19 | }, 20 | exclude: ['TERM'], 21 | regex: '', 22 | regex_flags: '' 23 | }, 24 | range: { 25 | __template: { 26 | field: 'FIELD', 27 | ranges: [ 28 | { 29 | 'to': 50 30 | }, 31 | { 32 | 'from': 20, 33 | 'to': 70 34 | }, 35 | { 36 | 'from': 70, 37 | 'to': 120 38 | }, 39 | { 40 | 'from': 150 41 | } 42 | ] 43 | }, 44 | field: '{field}', 45 | ranges: [ 46 | { 47 | to: 10, 48 | from: 20 49 | } 50 | ] 51 | }, 52 | histogram: { 53 | __template: { 54 | field: 'FIELD', 55 | interval: 100 56 | }, 57 | field: '{field}', 58 | interval: 100, 59 | time_interval: '1.5h', 60 | key_field: '{field}', 61 | value_field: '{field}', 62 | key_script: '', 63 | value_script: '', 64 | params: {} 65 | }, 66 | date_histogram: { 67 | __template: { 68 | field: 'FIELD', 69 | 'interval': 'day' 70 | }, 71 | field: '{field}', 72 | interval: { 73 | __one_of: ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', '1h', '1d', '1w'] 74 | }, 75 | post_zone: -1, 76 | pre_zone: -1, 77 | factor: 1000, 78 | pre_offset: '1d', 79 | post_offset: '1d', 80 | key_field: '{field}', 81 | value_field: '{field}', 82 | value_script: '' 83 | }, 84 | filter: {}, 85 | query: {}, 86 | facet_filter: { 87 | __scope_link: 'GLOBAL.filter' 88 | }, 89 | statistical: { 90 | __template: { 91 | field: 'FIELD' 92 | }, 93 | field: '{field}', 94 | fields: ['{field}'], 95 | script: '' 96 | }, 97 | terms_stats: { 98 | __template: { 99 | key_field: 'FIELD', 100 | value_field: 'FIELD' 101 | }, 102 | key_field: '{field}', 103 | value_field: '{field}', 104 | value_script: '', 105 | size: 10, 106 | order: { 107 | __one_of: ['count', 'term', 'reverse_term', 'reverse_count', 'total', 'reverse_total', 108 | 'min', 'reverse_min', 'max', 'reverse_max', 'mean', 'reverse_mean' 109 | ] 110 | } 111 | } 112 | } 113 | }); 114 | }; 115 | -------------------------------------------------------------------------------- /api_server/es_1_0/globals.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addGlobalAutocompleteRules('highlight', { 3 | pre_tags: {}, 4 | post_tags: {}, 5 | tags_schema: {}, 6 | fields: { 7 | '{field}': { 8 | fragment_size: 20, 9 | number_of_fragments: 3 10 | } 11 | } 12 | }); 13 | 14 | // only used with scope links as there is no common name for scripts 15 | api.addGlobalAutocompleteRules('SCRIPT_ENV', { 16 | __template: {'script': ''}, 17 | script: '', 18 | lang: '', 19 | params: {} 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /api_server/es_1_0/indices.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | 3 | api.addEndpointDescription('_refresh', { 4 | methods: ['POST'], 5 | patterns: [ 6 | "_refresh", 7 | "{indices}/_refresh" 8 | ], 9 | }); 10 | 11 | api.addEndpointDescription('_flush', { 12 | methods: ['POST'], 13 | patterns: [ 14 | "_flush", 15 | "{indices}/_flush" 16 | ], 17 | url_params: { 18 | wait_if_ongoing: [true, false], 19 | force: [true, false] 20 | } 21 | }); 22 | 23 | api.addEndpointDescription('_flush_synced', { 24 | methods: ['POST'], 25 | patterns: [ 26 | "_flush/synced", 27 | "{indices}/_flush/synced" 28 | ] 29 | }); 30 | 31 | api.addEndpointDescription('_stats', { 32 | patterns: [ 33 | "_stats", 34 | "_stats/{metrics}", 35 | "{indices}/_stats", 36 | "{indices}/_stats/{metrics}", 37 | ], 38 | url_components: { 39 | "metrics": [ 40 | "docs", 41 | "store", 42 | "indexing", 43 | "search", 44 | "get", 45 | "merge", 46 | "refresh", 47 | "flush", 48 | "warmer", 49 | "filter_cache", 50 | "id_cache", 51 | "percolate", 52 | "segments", 53 | "fielddata", 54 | "completion", 55 | "translog", 56 | "query_cache", 57 | "commit", 58 | "_all" 59 | ] 60 | }, 61 | url_params: { 62 | "fields": [], 63 | "types": [], 64 | "completion_fields": [], 65 | "fielddata_fields": [], 66 | "level": ["cluster", "indices", "shards"] 67 | } 68 | 69 | }); 70 | 71 | api.addEndpointDescription('_segments', { 72 | patterns: [ 73 | "{indices}/_segments", 74 | "_segments" 75 | ] 76 | }); 77 | 78 | api.addEndpointDescription('_recovery', { 79 | patterns: [ 80 | "{indices}/_recovery", 81 | "_recovery" 82 | ], 83 | url_params: { 84 | detailed: "__flag__", 85 | active_only: "__flag__", 86 | human: "__flag__" 87 | } 88 | }); 89 | 90 | api.addEndpointDescription('_analyze', { 91 | methods: ['GET', 'POST'], 92 | patterns: [ 93 | "{indices}/_analyze", 94 | "_analyze" 95 | ], 96 | url_params: { 97 | "analyzer": "", 98 | "char_filters": [], 99 | "field": "", 100 | "filters": [], 101 | "text": "", 102 | "tokenizer": "" 103 | } 104 | }); 105 | 106 | api.addEndpointDescription('_validate_query', { 107 | methods: ['GET', 'POST'], 108 | patterns: [ 109 | "{indices}/_validate/query", 110 | "_validate/query" 111 | ], 112 | url_params: { 113 | explain: "__flag__", 114 | rewrite: "__flag__" 115 | }, 116 | data_autocomplete_rules: { 117 | query: { 118 | // populated by a global rule 119 | } 120 | } 121 | }); 122 | 123 | api.addEndpointDescription('__create_index__', { 124 | methods: ['PUT'], 125 | patterns: [ 126 | "{index}" 127 | ], 128 | data_autocomplete_rules: { 129 | mappings: { 130 | __scope_link: '_put_mapping' 131 | }, 132 | settings: { 133 | __scope_link: '_put_settings' 134 | }, 135 | aliases: { 136 | __template: { 137 | "NAME": {} 138 | } 139 | } 140 | } 141 | }); 142 | 143 | api.addEndpointDescription('__delete_indices__', { 144 | methods: ['DELETE'], 145 | patterns: [ 146 | "{indices}" 147 | ] 148 | }); 149 | 150 | api.addEndpointDescription('_get_index_settings', { 151 | methods: ['GET',], 152 | patterns: [ 153 | "{indices}/_settings", 154 | ], 155 | url_params: { 156 | flat_settings: "__flag__" 157 | } 158 | }); 159 | 160 | api.addEndpointDescription('_get_index', { 161 | methods: ['GET',], 162 | patterns: [ 163 | "{indices}", 164 | "{indices}/{feature}" 165 | ], 166 | url_components: { 167 | "feature": [ 168 | "_mappings", 169 | "_warmers", 170 | "_aliases" 171 | ] 172 | } 173 | }); 174 | 175 | api.addEndpointDescription('_cache/clear', { 176 | patterns: [ 177 | "_cache/clear", 178 | "{indices}/_cache/clear" 179 | ] 180 | }); 181 | 182 | api.addEndpointDescription('_status', { 183 | patterns: [ 184 | "_status", 185 | "{indices}/_status" 186 | ] 187 | }); 188 | 189 | api.addEndpointDescription('_upgrade', { 190 | methods: ["POST"], 191 | patterns: [ 192 | "_upgrade", 193 | "{indices}/_upgrade" 194 | ], 195 | url_params: { 196 | wait_for_completion: "__flag__" 197 | } 198 | }); 199 | 200 | api.addEndpointDescription('_upgrade_status', { 201 | methods: ["GET"], 202 | patterns: [ 203 | "_upgrade", 204 | "{indices}/_upgrade" 205 | ] 206 | }); 207 | 208 | }; 209 | -------------------------------------------------------------------------------- /api_server/es_1_0/nodes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_nodes/hot_threads', { 3 | methods: ['GET'], 4 | patterns: [ 5 | "_nodes/hot_threads", 6 | "_nodes/{nodes}/hot_threads" 7 | ] 8 | }); 9 | api.addEndpointDescription('_nodes/info', { 10 | patterns: [ 11 | "_nodes", 12 | "_nodes/{metrics}", 13 | "_nodes/{nodes}", 14 | "_nodes/{nodes}/{metrics}", 15 | "_nodes/{nodes}/info/{metrics}" 16 | ], 17 | url_components: { 18 | "metrics": [ 19 | "settings", 20 | "os", 21 | "process", 22 | "jvm", 23 | "thread_pool", 24 | "network", 25 | "transport", 26 | "http", 27 | "plugins", 28 | "_all" 29 | ] 30 | } 31 | }); 32 | api.addEndpointDescription('_nodes/stats', { 33 | patterns: [ 34 | "_nodes/stats", 35 | "_nodes/stats/{metrics}", 36 | "_nodes/stats/{metrics}/{index_metric}", 37 | "_nodes/{nodes}/stats", 38 | "_nodes/{nodes}/stats/{metrics}", 39 | "_nodes/{nodes}/stats/{metrics}/{index_metric}" 40 | ], 41 | url_components: { 42 | "metrics": [ 43 | "os", 44 | "jvm", 45 | "thread_pool", 46 | "network", 47 | "fs", 48 | "transport", 49 | "http", 50 | "indices", 51 | "process", 52 | "breaker", 53 | "_all" 54 | ], 55 | "index_metric": [ 56 | "store", 57 | "indexing", 58 | "get", 59 | "search", 60 | "merge", 61 | "flush", 62 | "refresh", 63 | "filter_cache", 64 | "id_cache", 65 | "fielddata", 66 | "docs", 67 | "warmer", 68 | "percolate", 69 | "completion", 70 | "segments", 71 | "translog", 72 | "query_cache", 73 | "_all" 74 | ] 75 | } 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /api_server/es_1_0/percolator.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_put_percolator', { 3 | priority: 10, // to override doc 4 | methods: ['PUT', 'POST'], 5 | patterns: [ 6 | "{index}/.percolator/{id}" 7 | ], 8 | url_params: { 9 | "version": 1, 10 | "version_type": ["external", "internal"], 11 | "op_type": ["create"], 12 | "routing": "", 13 | "parent": "", 14 | "timestamp": "", 15 | "ttl": "5m", 16 | "consistency": ["qurom", "one", "all"], 17 | "replication": ["sync", "async"], 18 | "refresh": "__flag__", 19 | "timeout": "1m" 20 | }, 21 | data_autocomplete_rules: { 22 | query: {} 23 | } 24 | }); 25 | api.addEndpointDescription('_percolate', { 26 | methods: ['GET', 'POST'], 27 | priority: 10, // to override doc 28 | patterns: [ 29 | "{indices}/{type}/_percolate" 30 | ], 31 | url_params: { 32 | preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 33 | routing: "", 34 | ignore_unavailable: ["true", "false"], 35 | percolate_format: ["ids"] 36 | }, 37 | data_autocomplete_rules: { 38 | doc: {}, 39 | query: {}, 40 | filter: {}, 41 | size: 10, 42 | track_scores: {__one_of: [true, false]}, 43 | sort: "_score", 44 | aggs: {}, 45 | highlight: {} 46 | } 47 | }); 48 | api.addEndpointDescription('_percolate_id', { 49 | methods: ['GET', 'POST'], 50 | patterns: [ 51 | "{indices}/{type}/{id}/_percolate" 52 | ], 53 | url_params: { 54 | routing: "", 55 | ignore_unavailable: ["true", "false"], 56 | percolate_format: ["ids"], 57 | percolate_index: "{index}", 58 | percolate_type: "{type}", 59 | percolate_routing: "", 60 | percolate_preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 61 | version: 1, 62 | version_type: ["external", "internal"] 63 | }, 64 | data_autocomplete_rules: { 65 | query: {}, 66 | filter: {}, 67 | size: 10, 68 | track_scores: {__one_of: [true, false]}, 69 | sort: "_score", 70 | aggs: {}, 71 | highlight: {} 72 | } 73 | }); 74 | api.addEndpointDescription('_percolate_count', { 75 | methods: ['GET', 'POST'], 76 | patterns: [ 77 | "{indices}/{type}/_percolate/count" 78 | ], 79 | url_params: { 80 | preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 81 | routing: "", 82 | ignore_unavailable: ["true", "false"], 83 | percolate_format: ["ids"] 84 | }, 85 | data_autocomplete_rules: { 86 | doc: {}, 87 | query: {}, 88 | filter: {} 89 | } 90 | }); 91 | }; 92 | -------------------------------------------------------------------------------- /api_server/es_1_0/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | 3 | api.addEndpointDescription('_get_settings', { 4 | patterns: [ 5 | "{indices}/_settings", 6 | "_settings" 7 | ], 8 | url_params: { 9 | flat_settings: "__flag__" 10 | } 11 | }); 12 | api.addEndpointDescription('_put_settings', { 13 | methods: ['PUT'], 14 | patterns: [ 15 | "{indices}/_settings", 16 | "_settings" 17 | ], 18 | data_autocomplete_rules: { 19 | refresh_interval: '1s', 20 | number_of_shards: 5, 21 | number_of_replicas: 1, 22 | 'blocks.read_only': { 23 | __one_of: [false, true] 24 | }, 25 | 'blocks.read': { 26 | __one_of: [true, false] 27 | }, 28 | 'blocks.write': { 29 | __one_of: [true, false] 30 | }, 31 | 'blocks.metadata': { 32 | __one_of: [true, false] 33 | }, 34 | term_index_interval: 32, 35 | term_index_divisor: 1, 36 | 'translog.flush_threshold_ops': 5000, 37 | 'translog.flush_threshold_size': '200mb', 38 | 'translog.flush_threshold_period': '30m', 39 | 'translog.disable_flush': { 40 | __one_of: [true, false] 41 | }, 42 | 'cache.filter.max_size': '2gb', 43 | 'cache.filter.expire': '2h', 44 | 'gateway.snapshot_interval': '10s', 45 | routing: { 46 | allocation: { 47 | include: { 48 | tag: '' 49 | }, 50 | exclude: { 51 | tag: '' 52 | }, 53 | require: { 54 | tag: '' 55 | }, 56 | total_shards_per_node: -1 57 | } 58 | }, 59 | 'recovery.initial_shards': { 60 | __one_of: ['quorum', 'quorum-1', 'half', 'full', 'full-1'] 61 | }, 62 | 'ttl.disable_purge': { 63 | __one_of: [true, false] 64 | }, 65 | analysis: { 66 | analyzer: {}, 67 | tokenizer: {}, 68 | filter: {}, 69 | char_filter: {} 70 | }, 71 | 'cache.query.enable': { 72 | __one_of: [true, false] 73 | }, 74 | shadow_replicas: { 75 | __one_of: [true, false] 76 | }, 77 | shared_filesystem: { 78 | __one_of: [true, false] 79 | }, 80 | data_path: 'path' 81 | } 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /api_server/es_1_0/snapshot_restore.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('restore_snapshot', { 3 | methods: ['POST'], 4 | patterns: [ 5 | '_snapshot/{id}/{id}/_restore' 6 | ], 7 | url_params: { 8 | wait_for_completion: "__flag__" 9 | }, 10 | data_autocomplete_rules: { 11 | indices: "*", 12 | ignore_unavailable: {__one_of: [true, false]}, 13 | include_global_state: false, 14 | rename_pattern: "index_(.+)", 15 | rename_replacement: "restored_index_$1" 16 | } 17 | }); 18 | 19 | 20 | api.addEndpointDescription('single_snapshot', { 21 | methods: ['GET', 'DELETE'], 22 | patterns: [ 23 | '_snapshot/{id}/{id}' 24 | ] 25 | }); 26 | 27 | api.addEndpointDescription('all_snapshots', { 28 | methods: ['GET'], 29 | patterns: [ 30 | '_snapshot/{id}/_all' 31 | ] 32 | }); 33 | 34 | api.addEndpointDescription('put_snapshot', { 35 | methods: ['PUT'], 36 | patterns: [ 37 | '_snapshot/{id}/{id}' 38 | ], 39 | url_params: { 40 | wait_for_completion: "__flag__" 41 | }, 42 | data_autocomplete_rules: { 43 | indices: "*", 44 | ignore_unavailable: {__one_of: [true, false]}, 45 | include_global_state: {__one_of: [true, false]}, 46 | partial: {__one_of: [true, false]} 47 | } 48 | }); 49 | 50 | api.addEndpointDescription('_snapshot_status', { 51 | methods: ['GET'], 52 | patterns: [ 53 | '_snapshot/_status', 54 | '_snapshot/{id}/_status', 55 | '_snapshot/{id}/{ids}/_status' 56 | ] 57 | }); 58 | 59 | 60 | function getRepositoryType(context, editor) { 61 | var iter = editor.iterForCurrentLoc(); 62 | // for now just iterate back to the first "type" key 63 | var t = iter.getCurrentToken(); 64 | var type; 65 | while (t && t.type.indexOf("url") < 0) { 66 | if (t.type === 'variable' && t.value === '"type"') { 67 | t = editor.parser.nextNonEmptyToken(iter); 68 | if (!t || t.type !== "punctuation.colon") { 69 | // weird place to be in, but safe choice.. 70 | break; 71 | } 72 | t = editor.parser.nextNonEmptyToken(iter); 73 | if (t && t.type === "string") { 74 | type = t.value.replace(/"/g, ''); 75 | } 76 | break; 77 | } 78 | t = editor.parser.prevNonEmptyToken(iter); 79 | } 80 | return type; 81 | } 82 | 83 | api.addEndpointDescription('put_repository', { 84 | methods: ['PUT'], 85 | patterns: [ 86 | '_snapshot/{id}' 87 | ], 88 | data_autocomplete_rules: { 89 | __template: {"type": ""}, 90 | 91 | "type": { 92 | __one_of: ["fs", "url", "s3", "hdfs"] 93 | }, 94 | "settings": { 95 | __one_of: [{ 96 | //fs 97 | __condition: { 98 | lines_regex: String.raw`type["']\s*:\s*["']fs` 99 | }, 100 | __template: { 101 | location: "path" 102 | }, 103 | location: "path", 104 | compress: {__one_of: [true, false]}, 105 | concurrent_streams: 5, 106 | chunk_size: "10m", 107 | max_restore_bytes_per_sec: "20mb", 108 | max_snapshot_bytes_per_sec: "20mb" 109 | }, 110 | {// url 111 | __condition: { 112 | lines_regex: String.raw`type["']\s*:\s*["']url` 113 | }, 114 | __template: { 115 | url: "" 116 | }, 117 | url: "", 118 | concurrent_streams: 5 119 | }, 120 | { //s3 121 | __condition: { 122 | lines_regex: String.raw`type["']\s*:\s*["']s3` 123 | }, 124 | __template: { 125 | bucket: "" 126 | }, 127 | bucket: "", 128 | region: "", 129 | base_path: "", 130 | concurrent_streams: 5, 131 | chunk_size: "10m", 132 | compress: {__one_of: [true, false]} 133 | }, 134 | {// hdfs 135 | __condition: { 136 | lines_regex: String.raw`type["']\s*:\s*["']hdfs` 137 | }, 138 | __template: { 139 | path: "" 140 | }, 141 | uri: "", 142 | path: "some/path", 143 | load_defaults: {__one_of: [true, false]}, 144 | conf_location: "cfg.xml", 145 | concurrent_streams: 5, 146 | compress: {__one_of: [true, false]}, 147 | chunk_size: "10m" 148 | } 149 | ] 150 | } 151 | } 152 | }); 153 | }; 154 | -------------------------------------------------------------------------------- /api_server/es_1_0/templates.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_delete_template', { 3 | methods: ['DELETE'], 4 | patterns: [ 5 | "_template/{id}", 6 | ] 7 | }); 8 | api.addEndpointDescription('_get_template', { 9 | methods: ['GET'], 10 | patterns: [ 11 | "_template/{id}", 12 | "_template", 13 | ] 14 | }); 15 | api.addEndpointDescription('_put_template', { 16 | methods: ['PUT'], 17 | patterns: [ 18 | "_template/{id}", 19 | ], 20 | data_autocomplete_rules: { 21 | template: 'index*', 22 | warmers: {__scope_link: '_warmer'}, 23 | mappings: {__scope_link: '_put_mapping'}, 24 | settings: {__scope_link: '_put_settings'} 25 | } 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /api_server/es_1_0/warmers.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_get_warmer', { 3 | patterns: ["_warmer", "_warmer/{name}", "{indices}/_warmer", "{indices}/_warmer/{name}"] 4 | }); 5 | api.addEndpointDescription('_delete_warmer', { 6 | methods: ['DELETE'], 7 | patterns: [ 8 | "{indices}/_warmer/{name}" 9 | ] 10 | }); 11 | api.addEndpointDescription('_put_warmer', { 12 | methods: ['PUT'], 13 | patterns: [ 14 | "{indices}/_warmer/{name}", 15 | "{indices}/{types}/_warmer/{name}" 16 | ], 17 | data_autocomplete_rules: { 18 | query: { 19 | // populated by a global rule 20 | }, 21 | facets: { 22 | // populated by a global rule 23 | }, 24 | aggs: {}, 25 | sort: { 26 | __scope_link: "_search.sort" 27 | } 28 | } 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /api_server/es_2_0.js: -------------------------------------------------------------------------------- 1 | let _ = require("lodash"); 2 | let Api = require('./api'); 3 | let parts = [ 4 | require('./es_2_0/aliases'), 5 | require('./es_2_0/aggregations'), 6 | require('./es_2_0/cat'), 7 | require('./es_2_0/cluster'), 8 | require('./es_2_0/count'), 9 | require('./es_2_0/document'), 10 | require('./es_2_0/field_stats'), 11 | require('./es_2_0/filter'), 12 | require('./es_2_0/nodes'), 13 | require('./es_2_0/globals'), 14 | require('./es_2_0/indices'), 15 | require('./es_2_0/mappings'), 16 | require('./es_2_0/percolator'), 17 | require('./es_2_0/query'), 18 | require('./es_2_0/snapshot_restore'), 19 | require('./es_2_0/search'), 20 | require('./es_2_0/settings'), 21 | require('./es_2_0/templates'), 22 | require('./es_2_0/warmers') 23 | ]; 24 | 25 | function ES_2_0() { 26 | Api.call(this, "es_2_0"); 27 | _.each(parts, function (apiSection) { 28 | apiSection(this); 29 | }, this); 30 | } 31 | 32 | ES_2_0.prototype = _.create(Api.prototype, {'constructor': ES_2_0}); 33 | 34 | (function (cls) { 35 | cls.addEndpointDescription = function (endpoint, description) { 36 | if (description) { 37 | var url_params_def = {}; 38 | _.each(description.patterns || [], function (p) { 39 | if (p.indexOf("{indices}") >= 0) { 40 | url_params_def["ignore_unavailable"] = "__flag__"; 41 | url_params_def["allow_no_indices"] = "__flag__"; 42 | url_params_def["expand_wildcards"] = ["open", "closed"]; 43 | } 44 | }); 45 | 46 | if (url_params_def) { 47 | description.url_params = description.url_params || {}; 48 | _.defaults(description.url_params, url_params_def); 49 | } 50 | } 51 | Object.getPrototypeOf(cls).addEndpointDescription.call(this, endpoint, description); 52 | }; 53 | })(ES_2_0.prototype); 54 | 55 | module.exports = new ES_2_0(); 56 | -------------------------------------------------------------------------------- /api_server/es_2_0/aliases.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_post_aliases', { 3 | methods: ['POST'], 4 | patterns: [ 5 | "_aliases", 6 | ], 7 | data_autocomplete_rules: { 8 | 'actions': { 9 | __template: [ 10 | {'add': {'index': 'test1', 'alias': 'alias1'}} 11 | ], 12 | __any_of: [ 13 | { 14 | add: { 15 | index: '{index}', 16 | alias: '', 17 | filter: {}, 18 | routing: '1', 19 | search_routing: '1,2', 20 | index_routing: '1' 21 | }, 22 | remove: { 23 | index: '', 24 | alias: '' 25 | } 26 | } 27 | ] 28 | } 29 | } 30 | }); 31 | api.addEndpointDescription('_get_aliases', { 32 | methods: ['GET'], 33 | patterns: [ 34 | "_aliases", 35 | ] 36 | }); 37 | 38 | var aliasRules = { 39 | filter: {}, 40 | routing: '1', 41 | search_routing: '1,2', 42 | index_routing: '1' 43 | }; 44 | 45 | api.addEndpointDescription('_post_alias', { 46 | methods: ["POST", "PUT"], 47 | patterns: [ 48 | "{indices}/_alias/{name}" 49 | ], 50 | data_autocomplete_rules: aliasRules 51 | }); 52 | api.addEndpointDescription('_delete_alias', { 53 | methods: ["DELETE"], 54 | patterns: [ 55 | "{indices}/_alias/{name}" 56 | ] 57 | }); 58 | api.addEndpointDescription('_get_alias', { 59 | methods: ["GET"], 60 | patterns: [ 61 | "_alias", 62 | "{indices}/_alias", 63 | "{indices}/_alias/{name}", 64 | "_alias/{name}" 65 | ] 66 | }); 67 | 68 | api.addGlobalAutocompleteRules('aliases', { 69 | '*': aliasRules 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /api_server/es_2_0/cat.js: -------------------------------------------------------------------------------- 1 | let _ = require("lodash"); 2 | 3 | function addSimpleCat(endpoint, api, params, patterns) { 4 | var url_params = {"help": "__flag__", "v": "__flag__", "bytes": ["b"]}; 5 | _.each(params || [], function (p) { 6 | if (_.isString(p)) { 7 | url_params[p] = "__flag__"; 8 | } 9 | else { 10 | var k = Object.keys(p)[0]; 11 | url_params[k] = p[k]; 12 | } 13 | }); 14 | api.addEndpointDescription(endpoint, { 15 | match: endpoint, 16 | url_params: url_params, 17 | patterns: patterns || [endpoint] 18 | }); 19 | } 20 | 21 | function addNodeattrsCat(api) { 22 | api.addEndpointDescription('_cat/nodeattrs', { 23 | methods: ['GET'], 24 | patterns: [ 25 | "_cat/nodeattrs" 26 | ], 27 | url_params: { 28 | help: "__flag__", 29 | v: "__flag__", 30 | h: ["node", "name", "id", "nodeId", "pid", "p", "host", "h", "ip", "i", "port", "po", "attr", "attr.name", "value", "attr.value"] 31 | } 32 | }); 33 | } 34 | 35 | module.exports = function (api) { 36 | addSimpleCat('_cat/aliases', api); 37 | addSimpleCat('_cat/allocation', api, null, ['_cat/allocation', '_cat/allocation/{nodes}']); 38 | addSimpleCat('_cat/count', api); 39 | addSimpleCat('_cat/health', api, [ 40 | {"ts": ["false", "true"]} 41 | ]); 42 | addSimpleCat('_cat/indices', api, [ 43 | {h: []}, 44 | "pri", 45 | ], 46 | ['_cat/indices', '_cat/indices/{indices}']); 47 | addSimpleCat('_cat/master', api); 48 | addSimpleCat('_cat/nodes', api); 49 | addSimpleCat('_cat/pending_tasks', api); 50 | addSimpleCat('_cat/recovery', api); 51 | addSimpleCat('_cat/thread_pool', api); 52 | addSimpleCat('_cat/shards', api); 53 | addSimpleCat('_cat/plugins', api); 54 | addSimpleCat('_cat/segments', api); 55 | addNodeattrsCat(api); 56 | }; 57 | -------------------------------------------------------------------------------- /api_server/es_2_0/cluster.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_cluster/nodes/stats'); 3 | api.addEndpointDescription('_cluster/state', { 4 | patterns: [ 5 | "_cluster/state", 6 | "_cluster/state/{metrics}", 7 | "_cluster/state/{metrics}/{indices}" 8 | ], 9 | url_components: { 10 | "metrics": ["version", "master_node", "nodes", "routing_table", "routing_node", "metadata", "blocks"] 11 | } 12 | }); 13 | api.addEndpointDescription('_cluster/health', { 14 | url_params: { 15 | "local": "__flag__", 16 | "level": ["indices", "shards"], 17 | "master_timeout": "30s", 18 | "timeout": "30s", 19 | "wait_for_status": ["yellow", "green"], 20 | "wait_for_relocating_shards": 0, 21 | "wait_for_active_shards": 0, 22 | "wait_for_nodes": 0 23 | } 24 | }); 25 | api.addEndpointDescription('_cluster/pending_tasks'); 26 | api.addEndpointDescription('get_cluster/settings', { 27 | patterns: [ 28 | '_cluster/settings' 29 | ] 30 | }); 31 | 32 | api.addEndpointDescription('put_cluster/settings', { 33 | methods: ['PUT'], 34 | patterns: [ 35 | '_cluster/settings' 36 | ], 37 | data_autocomplete_rules: { 38 | persistent: { 39 | cluster: { 40 | routing: { 41 | 'allocation.enable': {__one_of: ["all", "primaries", "new_primaries", "none"]}, 42 | 'allocation.disk.threshold_enabled': {__one_of: [false, true]}, 43 | 'allocation.disk.watermark.low': '85%', 44 | 'allocation.disk.watermark.high': '90%', 45 | 'allocation.disk.include_relocations': {__one_of: [true, false]}, 46 | 'allocation.disk.reroute_interval': '60s', 47 | 'allocation.exclude': { 48 | '_ip': "", 49 | '_name': "", 50 | '_host': "", 51 | '_id': "" 52 | }, 53 | 'allocation.include': { 54 | '_ip': "", 55 | '_name': "", 56 | '_host': "", 57 | '_id': "" 58 | }, 59 | 'allocation.require': { 60 | '_ip': "", 61 | '_name': "", 62 | '_host': "", 63 | '_id': "" 64 | }, 65 | 'allocation.awareness.attributes': [], 66 | 'allocation.awareness.force': { 67 | '*': { 68 | 'values': [] 69 | } 70 | }, 71 | 'allocation.allow_rebalance': {__one_of: ['always', 'indices_primaries_active', 'indices_all_active']}, 72 | 'allocation.cluster_concurrent_rebalance': 2, 73 | 'allocation.node_initial_primaries_recoveries': 4, 74 | 'allocation.node_concurrent_recoveries': 2, 75 | 'allocation.same_shard.host': {__one_of: [false, true]} 76 | } 77 | }, 78 | indices: { 79 | breaker: { 80 | "total.limit": "70%", 81 | "fielddata.limit": "60%", 82 | "fielddata.overhead": 1.03, 83 | "request.limit": "40%", 84 | "request.overhead": 1.0 85 | } 86 | } 87 | }, 88 | transient: { 89 | __scope_link: '.persistent' 90 | } 91 | } 92 | }); 93 | 94 | api.addEndpointDescription('_cluster/reroute', { 95 | methods: ['POST'], 96 | url_params: { 97 | explain: "__flag__", 98 | dry_run: "__flag__" 99 | }, 100 | data_autocomplete_rules: { 101 | commands: [ 102 | { 103 | move: { 104 | __template: { 105 | index: "", 106 | shard: 0, 107 | from_node: "", 108 | to_node: "" 109 | }, 110 | index: "{index}", 111 | shard: 0, 112 | from_node: "{node}", 113 | to_node: "{node}" 114 | }, 115 | cancel: { 116 | __template: { 117 | index: "", 118 | shard: 0, 119 | node: "" 120 | }, 121 | index: "{index}", 122 | shard: 0, 123 | node: "{node}", 124 | allow_primary: {__one_of: [true, false]} 125 | }, 126 | allocate: { 127 | __template: { 128 | index: "", 129 | shard: 0, 130 | node: "" 131 | }, 132 | index: "{index}", 133 | shard: 0, 134 | node: "{node}", 135 | allow_primary: {__one_of: [true, false]} 136 | } 137 | } 138 | ], 139 | dry_run: {__one_of: [true, false]} 140 | } 141 | }); 142 | }; 143 | -------------------------------------------------------------------------------- /api_server/es_2_0/count.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_count', { 3 | methods: ['GET', 'POST'], 4 | priority: 10, // collides with get doc by id 5 | patterns: [ 6 | "{indices}/{types}/_count", 7 | "{indices}/_count", 8 | "_count" 9 | ], 10 | url_params: { 11 | preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 12 | routing: "", 13 | min_score: 1.0, 14 | terminate_after: 10, 15 | }, 16 | data_autocomplete_rules: { 17 | query: { 18 | // populated by a global rule 19 | } 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /api_server/es_2_0/field_stats.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_field_stats', { 3 | methods: ['GET', 'POST'], 4 | patterns: [ 5 | "_field_stats", 6 | "{indices}/_field_stats" 7 | ], 8 | url_params: { 9 | fields: [], 10 | level: ["cluster", "indices"], 11 | ignore_unavailable: ["true", "false"], 12 | allow_no_indices: [false, true], 13 | expand_wildcards: ["open", "closed", "none", "all"] 14 | }, 15 | data_autocomplete_rules: { 16 | fields: [ 17 | "{field}", 18 | ], 19 | index_constraints: { 20 | "{field}": { 21 | min_value: { 22 | gt: "MIN", 23 | gte: "MAX", 24 | lt: "MIN", 25 | lte: "MAX" 26 | }, 27 | max_value: { 28 | gt: "MIN", 29 | gte: "MAX", 30 | lt: "MIN", 31 | lte: "MAX" 32 | } 33 | }, 34 | __template: { 35 | "FIELD": { 36 | min_value: { 37 | gt: "MIN" 38 | }, 39 | max_value: { 40 | lt: "MAX" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /api_server/es_2_0/filter.js: -------------------------------------------------------------------------------- 1 | var filters = {}; 2 | 3 | filters.and = { 4 | __template: { 5 | filters: [ 6 | {} 7 | ] 8 | }, 9 | filters: [ 10 | { 11 | __scope_link: '.' 12 | } 13 | ], 14 | }; 15 | 16 | 17 | filters.bool = { 18 | __scope_link: 'GLOBAL.query' 19 | }; 20 | 21 | 22 | filters.exists = { 23 | __template: { 24 | 'field': 'FIELD_NAME' 25 | }, 26 | 'field': '{field}' 27 | }; 28 | 29 | 30 | filters.ids = { 31 | __template: { 32 | 'values': ['ID'] 33 | }, 34 | 'type': '{type}', 35 | 'values': [''] 36 | }; 37 | 38 | 39 | filters.limit = { 40 | __template: { 41 | value: 100 42 | }, 43 | value: 100 44 | }; 45 | 46 | 47 | filters.type = { 48 | __template: { 49 | value: 'TYPE' 50 | }, 51 | value: '{type}' 52 | }; 53 | 54 | 55 | filters.geo_bounding_box = { 56 | __template: { 57 | 'FIELD': { 58 | 'top_left': { 59 | 'lat': 40.73, 60 | 'lon': -74.1 61 | }, 62 | 'bottom_right': { 63 | 'lat': 40.717, 64 | 'lon': -73.99 65 | } 66 | } 67 | }, 68 | 69 | '{field}': { 70 | top_left: { 71 | lat: 40.73, 72 | lon: -74.1 73 | }, 74 | bottom_right: { 75 | lat: 40.73, 76 | lon: -74.1 77 | } 78 | }, 79 | type: { 80 | __one_of: ['memory', 'indexed'] 81 | }, 82 | }; 83 | 84 | 85 | filters.geo_distance = { 86 | __template: { 87 | distance: 100, 88 | distance_unit: 'km', 89 | 'FIELD': { 90 | lat: 40.73, 91 | lon: -74.1 92 | } 93 | }, 94 | distance: 100, 95 | distance_unit: { 96 | __one_of: ['km', 'miles'] 97 | }, 98 | distance_type: { 99 | __one_of: ['arc', 'plane'] 100 | }, 101 | optimize_bbox: { 102 | __one_of: ['memory', 'indexed', 'none'] 103 | }, 104 | '{field}': { 105 | lat: 40.73, 106 | lon: -74.1 107 | }, 108 | }; 109 | 110 | 111 | filters.geo_distance_range = { 112 | __template: { 113 | from: 100, 114 | to: 200, 115 | distance_unit: 'km', 116 | 'FIELD': { 117 | lat: 40.73, 118 | lon: -74.1 119 | } 120 | }, 121 | from: 100, 122 | to: 200, 123 | 124 | distance_unit: { 125 | __one_of: ['km', 'miles'] 126 | }, 127 | distance_type: { 128 | __one_of: ['arc', 'plane'] 129 | }, 130 | include_lower: { 131 | __one_of: [true, false] 132 | }, 133 | include_upper: { 134 | __one_of: [true, false] 135 | }, 136 | 137 | '{field}': { 138 | lat: 40.73, 139 | lon: -74.1 140 | } 141 | }; 142 | 143 | 144 | filters.geo_polygon = { 145 | __template: { 146 | 'FIELD': { 147 | 'points': [ 148 | { 149 | lat: 40.73, 150 | lon: -74.1 151 | }, 152 | { 153 | lat: 40.83, 154 | lon: -75.1 155 | } 156 | ] 157 | } 158 | }, 159 | '{field}': { 160 | points: [ 161 | { 162 | lat: 40.73, 163 | lon: -74.1 164 | } 165 | ] 166 | } 167 | }; 168 | 169 | 170 | filters.geo_shape = { 171 | __template: { 172 | 'FIELD': { 173 | shape: { 174 | type: 'envelope', 175 | coordinates: [ 176 | [-45, 45], 177 | [45, -45] 178 | ] 179 | }, 180 | 'relation': 'within' 181 | } 182 | }, 183 | '{field}': { 184 | shape: { 185 | type: '', 186 | coordinates: [] 187 | }, 188 | indexed_shape: { 189 | id: '', 190 | index: '{index}', 191 | type: '{type}', 192 | shape_field_name: 'shape' 193 | }, 194 | relation: { 195 | __one_of: ['within', 'intersects', 'disjoint'] 196 | } 197 | } 198 | }; 199 | 200 | 201 | filters.has_child = { 202 | __template: { 203 | type: 'TYPE', 204 | filter: {} 205 | }, 206 | type: '{type}', 207 | query: {}, 208 | filter: {}, 209 | _scope: '', 210 | min_children: 1, 211 | max_children: 10 212 | }; 213 | 214 | 215 | filters.has_parent = { 216 | __template: { 217 | parent_type: 'TYPE', 218 | filter: {} 219 | }, 220 | parent_type: '{type}', 221 | query: {}, 222 | filter: {}, 223 | _scope: '' 224 | }; 225 | 226 | 227 | filters.m = filters.missing = { 228 | __template: { 229 | field: 'FIELD' 230 | }, 231 | existence: { 232 | __one_of: [true, false] 233 | }, 234 | null_value: { 235 | __one_of: [true, false] 236 | }, 237 | field: '{field}' 238 | }; 239 | 240 | 241 | filters.not = { 242 | __template: { 243 | filter: {} 244 | }, 245 | filter: {} 246 | }; 247 | 248 | 249 | filters.range = { 250 | __template: { 251 | 'FIELD': { 252 | gte: 10, 253 | lte: 20 254 | } 255 | }, 256 | "{field}": { 257 | gte: 1, 258 | gt: 1, 259 | lte: 20, 260 | lt: 20, 261 | time_zone: "+1:00", 262 | "format": "dd/MM/yyyy||yyyy", 263 | execution: {__one_of: ["index", "fielddata"]} 264 | } 265 | }; 266 | 267 | 268 | filters.or = { 269 | __template: { 270 | filters: [ 271 | {} 272 | ] 273 | }, 274 | filters: [ 275 | { 276 | __scope_link: '.' 277 | } 278 | ] 279 | }; 280 | 281 | 282 | filters.prefix = { 283 | __template: { 284 | 'FIELD': 'VALUE' 285 | }, 286 | '{field}': '' 287 | }; 288 | 289 | 290 | filters.query = { 291 | // global query 292 | }; 293 | 294 | filters.script = { 295 | __template: { 296 | script: {} 297 | }, 298 | script: { 299 | // populated by a global rule 300 | } 301 | }; 302 | 303 | 304 | filters.term = { 305 | __template: { 306 | 'FIELD': 'VALUE' 307 | }, 308 | '{field}': '' 309 | }; 310 | 311 | 312 | filters.terms = { 313 | __template: { 314 | 'FIELD': ['VALUE1', 'VALUE2'] 315 | }, 316 | field: ['{field}'], 317 | execution: { 318 | __one_of: ['plain', 'bool', 'and', 'or', 'bool_nocache', 'and_nocache', 'or_nocache'] 319 | } 320 | }; 321 | 322 | 323 | filters.nested = { 324 | __template: { 325 | path: 'path_to_nested_doc', 326 | query: {} 327 | }, 328 | query: {}, 329 | path: '', 330 | _name: '' 331 | }; 332 | 333 | module.exports = function (api) { 334 | api.addGlobalAutocompleteRules('filter', filters); 335 | }; 336 | 337 | -------------------------------------------------------------------------------- /api_server/es_2_0/globals.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addGlobalAutocompleteRules('highlight', { 3 | pre_tags: {}, 4 | post_tags: {}, 5 | tags_schema: {}, 6 | fields: { 7 | '{field}': { 8 | fragment_size: 20, 9 | number_of_fragments: 3 10 | } 11 | } 12 | }); 13 | 14 | api.addGlobalAutocompleteRules('script', { 15 | __template: { 16 | inline: "SCRIPT" 17 | }, 18 | inline: "SCRIPT", 19 | file: "FILE_SCRIPT_NAME", 20 | id: "SCRIPT_ID", 21 | lang: "", 22 | params: {} 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /api_server/es_2_0/indices.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_refresh', { 3 | methods: ['POST'], 4 | patterns: [ 5 | "_refresh", 6 | "{indices}/_refresh" 7 | ], 8 | }); 9 | 10 | api.addEndpointDescription('_flush', { 11 | methods: ['POST'], 12 | patterns: [ 13 | "_flush", 14 | "{indices}/_flush" 15 | ], 16 | url_params: { 17 | wait_if_ongoing: [true, false], 18 | force: [true, false] 19 | } 20 | }); 21 | 22 | api.addEndpointDescription('_flush_synced', { 23 | methods: ['POST'], 24 | patterns: [ 25 | "_flush/synced", 26 | "{indices}/_flush/synced" 27 | ] 28 | }); 29 | 30 | api.addEndpointDescription('_stats', { 31 | patterns: [ 32 | "_stats", 33 | "_stats/{metrics}", 34 | "{indices}/_stats", 35 | "{indices}/_stats/{metrics}", 36 | ], 37 | url_components: { 38 | "metrics": [ 39 | "docs", 40 | "store", 41 | "indexing", 42 | "search", 43 | "get", 44 | "merge", 45 | "refresh", 46 | "flush", 47 | "warmer", 48 | "filter_cache", 49 | "percolate", 50 | "segments", 51 | "fielddata", 52 | "completion", 53 | "translog", 54 | "query_cache", 55 | "commit", 56 | "_all" 57 | ] 58 | }, 59 | url_params: { 60 | "fields": [], 61 | "types": [], 62 | "completion_fields": [], 63 | "fielddata_fields": [], 64 | "level": ["cluster", "indices", "shards"] 65 | } 66 | 67 | }); 68 | 69 | api.addEndpointDescription('_segments', { 70 | patterns: [ 71 | "{indices}/_segments", 72 | "_segments" 73 | ] 74 | }); 75 | 76 | api.addEndpointDescription('_recovery', { 77 | patterns: [ 78 | "{indices}/_recovery", 79 | "_recovery" 80 | ], 81 | url_params: { 82 | detailed: "__flag__", 83 | active_only: "__flag__", 84 | human: "__flag__" 85 | } 86 | }); 87 | 88 | api.addEndpointDescription('_analyze', { 89 | methods: ['GET', 'POST'], 90 | patterns: [ 91 | "{indices}/_analyze", 92 | "_analyze" 93 | ], 94 | url_params: { 95 | "analyzer": "", 96 | "char_filters": [], 97 | "field": "", 98 | "filters": [], 99 | "text": "", 100 | "tokenizer": "" 101 | } 102 | }); 103 | 104 | api.addEndpointDescription('_validate_query', { 105 | methods: ['GET', 'POST'], 106 | patterns: [ 107 | "{indices}/_validate/query", 108 | "_validate/query" 109 | ], 110 | url_params: { 111 | explain: "__flag__", 112 | rewrite: "__flag__" 113 | }, 114 | data_autocomplete_rules: { 115 | query: { 116 | // populated by a global rule 117 | } 118 | } 119 | }); 120 | 121 | api.addEndpointDescription('_shard_stores', { 122 | methods: ['GET'], 123 | patterns: [ 124 | "{indices}/_shard_stores", 125 | "_shard_stores" 126 | ], 127 | url_params: { 128 | status: ["green", "yellow", "red", "all"] 129 | } 130 | }); 131 | 132 | api.addEndpointDescription('__create_index__', { 133 | methods: ['PUT'], 134 | patterns: [ 135 | "{index}" 136 | ], 137 | data_autocomplete_rules: { 138 | mappings: { 139 | __scope_link: '_put_mapping' 140 | }, 141 | settings: { 142 | __scope_link: '_put_settings' 143 | }, 144 | aliases: { 145 | __template: { 146 | "NAME": {} 147 | } 148 | } 149 | } 150 | }); 151 | 152 | api.addEndpointDescription('__delete_indices__', { 153 | methods: ['DELETE'], 154 | patterns: [ 155 | "{indices}" 156 | ] 157 | }); 158 | 159 | api.addEndpointDescription('_get_index_settings', { 160 | methods: ['GET',], 161 | patterns: [ 162 | "{indices}/_settings", 163 | ], 164 | url_params: { 165 | flat_settings: "__flag__" 166 | } 167 | }); 168 | 169 | api.addEndpointDescription('_get_index', { 170 | methods: ['GET',], 171 | patterns: [ 172 | "{indices}", 173 | "{indices}/{feature}" 174 | ], 175 | url_components: { 176 | "feature": [ 177 | "_mappings", 178 | "_warmers", 179 | "_aliases" 180 | ] 181 | } 182 | }); 183 | 184 | api.addEndpointDescription('_cache/clear', { 185 | patterns: [ 186 | "_cache/clear", 187 | "{indices}/_cache/clear" 188 | ] 189 | }); 190 | 191 | api.addEndpointDescription('_upgrade', { 192 | methods: ["POST"], 193 | patterns: [ 194 | "_upgrade", 195 | "{indices}/_upgrade" 196 | ], 197 | url_params: { 198 | wait_for_completion: "__flag__" 199 | } 200 | }); 201 | 202 | api.addEndpointDescription('_upgrade_status', { 203 | methods: ["GET"], 204 | patterns: [ 205 | "_upgrade", 206 | "{indices}/_upgrade" 207 | ] 208 | }); 209 | }; 210 | -------------------------------------------------------------------------------- /api_server/es_2_0/nodes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_nodes/hot_threads', { 3 | methods: ['GET'], 4 | patterns: [ 5 | "_nodes/hot_threads", 6 | "_nodes/{nodes}/hot_threads" 7 | ] 8 | }); 9 | api.addEndpointDescription('_nodes/info', { 10 | patterns: [ 11 | "_nodes", 12 | "_nodes/{metrics}", 13 | "_nodes/{nodes}", 14 | "_nodes/{nodes}/{metrics}", 15 | "_nodes/{nodes}/info/{metrics}" 16 | ], 17 | url_components: { 18 | "metrics": [ 19 | "settings", 20 | "os", 21 | "process", 22 | "jvm", 23 | "thread_pool", 24 | "network", 25 | "transport", 26 | "http", 27 | "plugins", 28 | "_all" 29 | ] 30 | } 31 | }); 32 | api.addEndpointDescription('_nodes/stats', { 33 | patterns: [ 34 | "_nodes/stats", 35 | "_nodes/stats/{metrics}", 36 | "_nodes/stats/{metrics}/{index_metric}", 37 | "_nodes/{nodes}/stats", 38 | "_nodes/{nodes}/stats/{metrics}", 39 | "_nodes/{nodes}/stats/{metrics}/{index_metric}" 40 | ], 41 | url_components: { 42 | "metrics": [ 43 | "os", 44 | "jvm", 45 | "thread_pool", 46 | "network", 47 | "fs", 48 | "transport", 49 | "http", 50 | "indices", 51 | "process", 52 | "breaker", 53 | "_all" 54 | ], 55 | "index_metric": [ 56 | "store", 57 | "indexing", 58 | "get", 59 | "search", 60 | "merge", 61 | "flush", 62 | "refresh", 63 | "filter_cache", 64 | "fielddata", 65 | "docs", 66 | "warmer", 67 | "percolate", 68 | "completion", 69 | "segments", 70 | "translog", 71 | "query_cache", 72 | "_all" 73 | ] 74 | } 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /api_server/es_2_0/percolator.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_put_percolator', { 3 | priority: 10, // to override doc 4 | methods: ['PUT', 'POST'], 5 | patterns: [ 6 | "{index}/.percolator/{id}" 7 | ], 8 | url_params: { 9 | "version": 1, 10 | "version_type": ["external", "internal"], 11 | "op_type": ["create"], 12 | "routing": "", 13 | "parent": "", 14 | "timestamp": "", 15 | "ttl": "5m", 16 | "consistency": ["qurom", "one", "all"], 17 | "refresh": "__flag__", 18 | "timeout": "1m" 19 | }, 20 | data_autocomplete_rules: { 21 | query: {} 22 | } 23 | }); 24 | api.addEndpointDescription('_percolate', { 25 | methods: ['GET', 'POST'], 26 | priority: 10, // to override doc 27 | patterns: [ 28 | "{indices}/{type}/_percolate" 29 | ], 30 | url_params: { 31 | preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 32 | routing: "", 33 | ignore_unavailable: ["true", "false"], 34 | percolate_format: ["ids"] 35 | }, 36 | data_autocomplete_rules: { 37 | doc: {}, 38 | query: {}, 39 | filter: {}, 40 | size: 10, 41 | track_scores: {__one_of: [true, false]}, 42 | sort: "_score", 43 | aggs: {}, 44 | highlight: {} 45 | } 46 | }); 47 | api.addEndpointDescription('_percolate_id', { 48 | methods: ['GET', 'POST'], 49 | patterns: [ 50 | "{indices}/{type}/{id}/_percolate" 51 | ], 52 | url_params: { 53 | routing: "", 54 | ignore_unavailable: ["true", "false"], 55 | percolate_format: ["ids"], 56 | percolate_index: "{index}", 57 | percolate_type: "{type}", 58 | percolate_routing: "", 59 | percolate_preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 60 | version: 1, 61 | version_type: ["external", "internal"] 62 | }, 63 | data_autocomplete_rules: { 64 | query: {}, 65 | filter: {}, 66 | size: 10, 67 | track_scores: {__one_of: [true, false]}, 68 | sort: "_score", 69 | aggs: {}, 70 | highlight: {} 71 | } 72 | }); 73 | api.addEndpointDescription('_percolate_count', { 74 | methods: ['GET', 'POST'], 75 | patterns: [ 76 | "{indices}/{type}/_percolate/count" 77 | ], 78 | url_params: { 79 | preference: ["_primary", "_primary_first", "_local", "_only_node:xyz", "_prefer_node:xyz", "_shards:2,3"], 80 | routing: "", 81 | ignore_unavailable: ["true", "false"], 82 | percolate_format: ["ids"] 83 | }, 84 | data_autocomplete_rules: { 85 | doc: {}, 86 | query: {}, 87 | filter: {} 88 | } 89 | }); 90 | }; 91 | -------------------------------------------------------------------------------- /api_server/es_2_0/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | 3 | api.addEndpointDescription('_get_settings', { 4 | patterns: [ 5 | "{indices}/_settings", 6 | "_settings" 7 | ], 8 | url_params: { 9 | flat_settings: "__flag__" 10 | } 11 | }); 12 | api.addEndpointDescription('_put_settings', { 13 | methods: ['PUT'], 14 | patterns: [ 15 | "{indices}/_settings", 16 | "_settings" 17 | ], 18 | data_autocomplete_rules: { 19 | refresh_interval: '1s', 20 | number_of_shards: 5, 21 | number_of_replicas: 1, 22 | 'blocks.read_only': { 23 | __one_of: [false, true] 24 | }, 25 | 'blocks.read': { 26 | __one_of: [true, false] 27 | }, 28 | 'blocks.write': { 29 | __one_of: [true, false] 30 | }, 31 | 'blocks.metadata': { 32 | __one_of: [true, false] 33 | }, 34 | term_index_interval: 32, 35 | term_index_divisor: 1, 36 | 'translog.flush_threshold_ops': 5000, 37 | 'translog.flush_threshold_size': '200mb', 38 | 'translog.flush_threshold_period': '30m', 39 | 'translog.disable_flush': { 40 | __one_of: [true, false] 41 | }, 42 | 'cache.filter.max_size': '2gb', 43 | 'cache.filter.expire': '2h', 44 | 'gateway.snapshot_interval': '10s', 45 | routing: { 46 | allocation: { 47 | include: { 48 | tag: '' 49 | }, 50 | exclude: { 51 | tag: '' 52 | }, 53 | require: { 54 | tag: '' 55 | }, 56 | total_shards_per_node: -1 57 | } 58 | }, 59 | 'recovery.initial_shards': { 60 | __one_of: ['quorum', 'quorum-1', 'half', 'full', 'full-1'] 61 | }, 62 | 'ttl.disable_purge': { 63 | __one_of: [true, false] 64 | }, 65 | analysis: { 66 | analyzer: {}, 67 | tokenizer: {}, 68 | filter: {}, 69 | char_filter: {} 70 | }, 71 | 'cache.query.enable': { 72 | __one_of: [true, false] 73 | }, 74 | shadow_replicas: { 75 | __one_of: [true, false] 76 | }, 77 | shared_filesystem: { 78 | __one_of: [true, false] 79 | }, 80 | data_path: 'path', 81 | codec: { 82 | __one_of: ['default', 'best_compression', 'lucene_default'] 83 | } 84 | } 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /api_server/es_2_0/snapshot_restore.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('restore_snapshot', { 3 | methods: ['POST'], 4 | patterns: [ 5 | '_snapshot/{id}/{id}/_restore' 6 | ], 7 | url_params: { 8 | wait_for_completion: "__flag__" 9 | }, 10 | data_autocomplete_rules: { 11 | indices: "*", 12 | ignore_unavailable: {__one_of: [true, false]}, 13 | include_global_state: false, 14 | rename_pattern: "index_(.+)", 15 | rename_replacement: "restored_index_$1" 16 | } 17 | }); 18 | 19 | 20 | api.addEndpointDescription('single_snapshot', { 21 | methods: ['GET', 'DELETE'], 22 | patterns: [ 23 | '_snapshot/{id}/{id}' 24 | ] 25 | }); 26 | 27 | api.addEndpointDescription('all_snapshots', { 28 | methods: ['GET'], 29 | patterns: [ 30 | '_snapshot/{id}/_all' 31 | ] 32 | }); 33 | 34 | api.addEndpointDescription('put_snapshot', { 35 | methods: ['PUT'], 36 | patterns: [ 37 | '_snapshot/{id}/{id}' 38 | ], 39 | url_params: { 40 | wait_for_completion: "__flag__" 41 | }, 42 | data_autocomplete_rules: { 43 | indices: "*", 44 | ignore_unavailable: {__one_of: [true, false]}, 45 | include_global_state: {__one_of: [true, false]}, 46 | partial: {__one_of: [true, false]} 47 | } 48 | }); 49 | 50 | api.addEndpointDescription('_snapshot_status', { 51 | methods: ['GET'], 52 | patterns: [ 53 | '_snapshot/_status', 54 | '_snapshot/{id}/_status', 55 | '_snapshot/{id}/{ids}/_status' 56 | ] 57 | }); 58 | 59 | 60 | function getRepositoryType(context, editor) { 61 | var iter = editor.iterForCurrentLoc(); 62 | // for now just iterate back to the first "type" key 63 | var t = iter.getCurrentToken(); 64 | var type; 65 | while (t && t.type.indexOf("url") < 0) { 66 | if (t.type === 'variable' && t.value === '"type"') { 67 | t = editor.parser.nextNonEmptyToken(iter); 68 | if (!t || t.type !== "punctuation.colon") { 69 | // weird place to be in, but safe choice.. 70 | break; 71 | } 72 | t = editor.parser.nextNonEmptyToken(iter); 73 | if (t && t.type === "string") { 74 | type = t.value.replace(/"/g, ''); 75 | } 76 | break; 77 | } 78 | t = editor.parser.prevNonEmptyToken(iter); 79 | } 80 | return type; 81 | } 82 | 83 | api.addEndpointDescription('put_repository', { 84 | methods: ['PUT'], 85 | patterns: [ 86 | '_snapshot/{id}' 87 | ], 88 | data_autocomplete_rules: { 89 | __template: {"type": ""}, 90 | 91 | "type": { 92 | __one_of: ["fs", "url", "s3", "hdfs"] 93 | }, 94 | "settings": { 95 | __one_of: [{ 96 | //fs 97 | __condition: { 98 | lines_regex: String.raw`type["']\s*:\s*["']fs` 99 | }, 100 | __template: { 101 | location: "path" 102 | }, 103 | location: "path", 104 | compress: {__one_of: [true, false]}, 105 | concurrent_streams: 5, 106 | chunk_size: "10m", 107 | max_restore_bytes_per_sec: "20mb", 108 | max_snapshot_bytes_per_sec: "20mb" 109 | }, 110 | {// url 111 | __condition: { 112 | lines_regex: String.raw`type["']\s*:\s*["']url` 113 | }, 114 | __template: { 115 | url: "" 116 | }, 117 | url: "", 118 | concurrent_streams: 5 119 | }, 120 | { //s3 121 | __condition: { 122 | lines_regex: String.raw`type["']\s*:\s*["']s3` 123 | }, 124 | __template: { 125 | bucket: "" 126 | }, 127 | bucket: "", 128 | region: "", 129 | base_path: "", 130 | concurrent_streams: 5, 131 | chunk_size: "10m", 132 | compress: {__one_of: [true, false]} 133 | }, 134 | {// hdfs 135 | __condition: { 136 | lines_regex: String.raw`type["']\s*:\s*["']hdfs` 137 | }, 138 | __template: { 139 | path: "" 140 | }, 141 | uri: "", 142 | path: "some/path", 143 | load_defaults: {__one_of: [true, false]}, 144 | conf_location: "cfg.xml", 145 | concurrent_streams: 5, 146 | compress: {__one_of: [true, false]}, 147 | chunk_size: "10m" 148 | } 149 | ] 150 | } 151 | } 152 | }); 153 | }; 154 | -------------------------------------------------------------------------------- /api_server/es_2_0/templates.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_delete_template', { 3 | methods: ['DELETE'], 4 | patterns: [ 5 | "_template/{id}", 6 | ] 7 | }); 8 | api.addEndpointDescription('_get_template', { 9 | methods: ['GET'], 10 | patterns: [ 11 | "_template/{id}", 12 | "_template", 13 | ] 14 | }); 15 | api.addEndpointDescription('_put_template', { 16 | methods: ['PUT'], 17 | patterns: [ 18 | "_template/{id}", 19 | ], 20 | data_autocomplete_rules: { 21 | template: 'index*', 22 | warmers: {__scope_link: '_warmer'}, 23 | mappings: {__scope_link: '_put_mapping'}, 24 | settings: {__scope_link: '_put_settings'} 25 | } 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /api_server/es_2_0/warmers.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.addEndpointDescription('_get_warmer', { 3 | patterns: ["_warmer", "_warmer/{name}", "{indices}/_warmer", "{indices}/_warmer/{name}"] 4 | }); 5 | api.addEndpointDescription('_delete_warmer', { 6 | methods: ['DELETE'], 7 | patterns: [ 8 | "{indices}/_warmer/{name}" 9 | ] 10 | }); 11 | api.addEndpointDescription('_put_warmer', { 12 | methods: ['PUT'], 13 | patterns: [ 14 | "{indices}/_warmer/{name}", 15 | "{indices}/{types}/_warmer/{name}" 16 | ], 17 | data_autocomplete_rules: { 18 | query: { 19 | // populated by a global rule 20 | }, 21 | facets: { 22 | // populated by a global rule 23 | }, 24 | aggs: {}, 25 | sort: { 26 | __scope_link: "_search.sort" 27 | } 28 | } 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /api_server/server.js: -------------------------------------------------------------------------------- 1 | let _ = require("lodash"); 2 | 3 | module.exports.resolveApi = function (sense_version, apis, reply) { 4 | let result = {}; 5 | _.each(apis, function (name) { 6 | { 7 | // for now we ignore sense_version. might add it in the api name later 8 | let api = require('./' + name); 9 | result[name] = api.asJson(); 10 | } 11 | }); 12 | 13 | return reply(result).type("application/json"); 14 | }; 15 | -------------------------------------------------------------------------------- /docs/images/auto_format_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/auto_format_after.png -------------------------------------------------------------------------------- /docs/images/auto_format_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/auto_format_before.png -------------------------------------------------------------------------------- /docs/images/auto_format_bulk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/auto_format_bulk.png -------------------------------------------------------------------------------- /docs/images/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/history.png -------------------------------------------------------------------------------- /docs/images/introduction_action_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/introduction_action_menu.png -------------------------------------------------------------------------------- /docs/images/introduction_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/introduction_output.png -------------------------------------------------------------------------------- /docs/images/introduction_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/introduction_screen.png -------------------------------------------------------------------------------- /docs/images/introduction_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/introduction_server.png -------------------------------------------------------------------------------- /docs/images/introduction_suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/introduction_suggestion.png -------------------------------------------------------------------------------- /docs/images/multiple_requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/multiple_requests.png -------------------------------------------------------------------------------- /docs/images/readme_api_suggestions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/readme_api_suggestions.png -------------------------------------------------------------------------------- /docs/images/readme_auto_formatting_mix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/readme_auto_formatting_mix.png -------------------------------------------------------------------------------- /docs/images/readme_copy_as_curl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/readme_copy_as_curl.png -------------------------------------------------------------------------------- /docs/images/readme_errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/readme_errors.png -------------------------------------------------------------------------------- /docs/images/readme_multiple_requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/readme_multiple_requests.png -------------------------------------------------------------------------------- /docs/images/readme_scope_collapsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/readme_scope_collapsing.png -------------------------------------------------------------------------------- /docs/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/docs/images/settings.png -------------------------------------------------------------------------------- /docs/index.asciidoc: -------------------------------------------------------------------------------- 1 | = Sense Documentation 2 | 3 | 4 | :kibanaversion: 4.2 5 | :senseversion: 2.0 6 | :forum: https://discuss.elastic.co/c/elasticsearch 7 | 8 | include::introduction.asciidoc[] 9 | 10 | include::installing_sense.asciidoc[] 11 | 12 | include::sense_ui.asciidoc[] 13 | 14 | include::release_notes.asciidoc[] -------------------------------------------------------------------------------- /docs/installing_sense.asciidoc: -------------------------------------------------------------------------------- 1 | [[installing]] 2 | == Installing Sense 3 | 4 | Sense is a Kibana app. To get up and running you will first need to download Kibana and install as instructed https://www.elastic.co/downloads/kibana[here]. 5 | You will need Kibana {kibanaversion} or higher. 6 | Once Kibana is installed, you can install Sense running the following command from your Kibana folder: 7 | 8 | [source,bash] 9 | ----------- 10 | $./bin/kibana plugin --install elastic/sense 11 | ----------- 12 | 13 | This will download and install the latest available version of Sense. 14 | 15 | You will now need to start Kibana: 16 | 17 | [source,bash] 18 | ----------- 19 | $./bin/kibana 20 | ----------- 21 | 22 | You should now be able to access Sense with a web browser on http://localhost:5601/app/sense (replace host and port 23 | if you configured them differently in Kibana). 24 | 25 | [NOTE] 26 | By default, Kibana 4.2 requires an active Elasticsearch instance of version 2.0 or higher. 27 | If you want to use Sense without this requirement 28 | see instructions <>. 29 | 30 | [[min_req]] 31 | === Minimum Requirements 32 | 33 | To run Sense you will need: 34 | 35 | - Kibana of version {kibanaversion} or later. 36 | - A https://www.elastic.co/support/matrix#matrix_browsers[modern browser] 37 | 38 | 39 | [[disable_discover]] 40 | === Running against Elasticsearch 1.x / disabling Kibana's Discover, Visualize & Dashboard apps 41 | 42 | Kibana ships by default with 3 handy apps called Discover, Visualize and Dashboard. These applications 43 | require an active Elasticsearch cluster of version 2.0 or higher. 44 | 45 | In order to use Sense with Elasticsearch 1.x, or if you don't want the overhead of the extra 46 | apps, add the following lines to your `config/kibana.yml` file: 47 | 48 | [source,yaml] 49 | ------------ 50 | kibana.enabled: false # disable the standard kibana discovery, visualize & dashboard plugins 51 | elasticsearch.enabled: false # do not require a running Elasticsearch 2.0 instance 52 | ------------ 53 | 54 | After this change you will need to restart Kibana. 55 | 56 | [[securing_sense]] 57 | === Securing Sense 58 | 59 | Sense is meant to be used as a local development tool. As such, it will send requests to any host & port combination, 60 | just as a local curl command would. To overcome the CORS limitations enforced by browsers, Sense's Node.js backend 61 | serves as a proxy to send requests on behalf of the browser. However, if put on a server and exposed to the internet 62 | this can become a security risk. In those cases, we highly recommend you lock down the proxy by setting the 63 | `sense.proxyFilter` setting. The setting accepts a list of regular expressions that are evaluated against each URL 64 | the proxy is requested to retrieve. If none of the regular expressions match the proxy will reject the request. 65 | 66 | Here is an example configuration the only allows Sense to connect to localhost: 67 | 68 | [source,yaml] 69 | -------- 70 | sense.proxyFilter: 71 | - ^https?://(localhost|127\.0\.0\.1|\[::0\]).* 72 | -------- 73 | 74 | You will need to restart Kibana for these changes to take effect. 75 | 76 | [[configuration]] 77 | === Configuration 78 | 79 | You can add the following options in the `config/kibana.yml` file: 80 | 81 | `sense.defaultServerUrl`:: 82 | The default value of the Server text box, where Sense sends all its requests to. 83 | Note that Sense remember the last server you used. This setting only has effect when Sense first loaded. 84 | Defaults to `localhost:9200` 85 | 86 | `sense.proxyFilter`:: A list of regular expressions that are used to validate any outgoing request from Sense. If none 87 | of these match, the request will be rejected. Defaults to `.*` . See <> for more details. 88 | 89 | `sense.proxyConfig`:: A list of configuration options that are based on the proxy target. Use this to set custom timeouts or SSL settings for specific hosts. This is done by defining a set of `match` criteria using wildcards/globs which will be checked against each request. The configuration from all matching rules will then be merged together to configure the proxy used for that request. 90 | + 91 | The valid match keys are `match.protocol`, `match.host`, `match.port`, and `match.path`. All of these keys default to `*`, which means they will match any value. 92 | + 93 | Example: 94 | + 95 | [source,yaml] 96 | -------- 97 | sense.proxyConfig: 98 | - match: 99 | host: "*.internal.org" # allow any host that ends in .internal.org 100 | port: "{9200..9299}" # allow any port from 9200-9299 101 | 102 | ssl: 103 | ca: "/opt/certs/internal.ca" 104 | # "key" and "cert" are also valid options here 105 | 106 | - match: 107 | protocol: "https" 108 | 109 | ssl: 110 | verify: false # allows any certificate to be used, even self-signed certs 111 | 112 | # since this rule has no "match" section it matches everything 113 | - timeout: 180000 # 3 minutes 114 | -------- 115 | 116 | [NOTE] 117 | 118 | Kibana needs to be restarted after each change to the configuration for them to be applied. 119 | 120 | [[manual_download]] 121 | === Manual Download 122 | 123 | Kibana’s `bin/plugin` script requires direct internet access for downloading and installing Sense. 124 | If your server doesn’t have internet access, you can download the Sense tar file from the following link: 125 | https://download.elasticsearch.org/elastic/sense/sense-latest.tar.gz 126 | 127 | Once downloaded you can install Sense using the following command: 128 | 129 | [source,bash] 130 | ------------- 131 | $ bin/kibana plugin -i sense -u file:///PATH_TO_SENSE_TAR_FILE 132 | ------------- 133 | -------------------------------------------------------------------------------- /docs/introduction.asciidoc: -------------------------------------------------------------------------------- 1 | [[introduction]] 2 | == Introduction 3 | 4 | Sense is a handy console for interacting with the REST API of Elasticsearch. As you can see below, Sense is composed of 5 | two main panes. The left pane, named the editor, is where you type the requests you will submit to Elasticsearch. 6 | The responses from Elasticsearch are shown on the right hand panel. The address of your Elasticsearch server should 7 | be entered in the text box on the top of screen (and defaults to `localhost:9200`). 8 | 9 | .The Sense UI 10 | image::images/introduction_screen.png[Screenshot] 11 | 12 | Sense understands commands in a cURL-like syntax. For example the following Sense command 13 | 14 | [source,js] 15 | ---------------------------------- 16 | GET /_search 17 | { 18 | "query": { 19 | "match_all": {} 20 | } 21 | } 22 | ---------------------------------- 23 | 24 | is a simple `GET` request to Elasticsearch's `_search API`. Here is the equivalent command in cURL. 25 | 26 | 27 | [source,bash] 28 | ---------------------------------- 29 | curl -XGET "http://localhost:9200/_search" -d' 30 | { 31 | "query": { 32 | "match_all": {} 33 | } 34 | }' 35 | ---------------------------------- 36 | 37 | 38 | In fact, you can paste the above command into Sense and it will automatically be converted into the Sense syntax. 39 | 40 | When typing a command, Sense will make context sensitive <>. These suggestions can help 41 | you explore parameters for each API, or to just speed up typing. Sense will suggest APIs, indexes and field 42 | names. 43 | 44 | [[suggestions]] 45 | .API suggestions 46 | image::images/introduction_suggestion.png["Suggestions",width=400,align="center"] 47 | 48 | 49 | Once you have typed a command in to the left pane, you can submit it to Elasticsearch by clicking the little green 50 | triangle that appears next to the url line of the request. Notice that as you move the cursor around, the little 51 | triangle and wrench icons follow you around. We call this the <>. You can also select 52 | multiple requests and submit them all at once. 53 | 54 | [[action_menu]] 55 | .The Action Menu 56 | image::images/introduction_action_menu.png["The Action Menu",width=400,align="center"] 57 | 58 | When the response comes back, you should see it in the right hand panel: 59 | 60 | .The Output Pane 61 | image::images/introduction_output.png[Screenshot] 62 | 63 | Sense allows you to easily switch between Elasticsearch instances. By default it will connect to `localhost:9200` 64 | but you can easily change this by entering a different url in the Server input: 65 | 66 | .The Server Input 67 | image::images/introduction_server.png["Server",width=400,align="center"] 68 | 69 | This pretty much covers the basics. To follow up, read the <> or dive deeper 70 | into the <>. 71 | 72 | [NOTE] 73 | Sense is a development tool and is configured by default to run on a laptop. If you install it on a server please 74 | look at the <> for instructions on how make it secure. 75 | -------------------------------------------------------------------------------- /docs/sense_ui.asciidoc: -------------------------------------------------------------------------------- 1 | [[sense-ui]] 2 | == The Sense UI 3 | 4 | In this section you will find a more detailed description of UI of Sense. The basic aspects of the UI are explained 5 | in the <> section. 6 | 7 | [[multi-req]] 8 | === Multiple Requests Support 9 | 10 | The Sense editor allows writing multiple requests below each other. As shown in the <> section, you 11 | can submit a request to Elasticsearch by positioning the cursor and using the <>. Similarly 12 | you can select multiple requests in one go: 13 | 14 | .Selecting Multiple Requests 15 | image::images/multiple_requests.png[Multiple Requests] 16 | 17 | Sense will send the request one by one to Elasticsearch and show the output on the right pane as Elasticsearch responds. 18 | This is very handy when debugging an issue or trying query combinations in multiple scenarios. 19 | 20 | Selecting multiple requests also allows you to auto format and copy them as cURL in one go. 21 | 22 | 23 | [[auto_formatting]] 24 | === Auto Formatting 25 | 26 | Sense allows you to auto format messy requests. To do so, position the cursor on the request you would like to format 27 | and select Auto Indent from the action menu: 28 | 29 | .Auto Indent a request 30 | image::images/auto_format_before.png["Auto format before",width=500,align="center"] 31 | 32 | Sense will adjust the JSON body of the request and it will now look like this: 33 | 34 | .A formatted request 35 | image::images/auto_format_after.png["Auto format after",width=500,align="center"] 36 | 37 | If you select Auto Indent on a request that is already perfectly formatted, Sense will collapse the 38 | request body to a single line per document. This is very handy when working with Elasticsearch's bulk APIs: 39 | 40 | .One doc per line 41 | image::images/auto_format_bulk.png["Auto format bulk",width=550,align="center"] 42 | 43 | 44 | [[keyboard_shortcuts]] 45 | === Keyboard shortcuts 46 | 47 | Sense comes with a set of nifty keyboard shortcuts making working with it even more efficient. Here is an overview: 48 | 49 | ==== General editing 50 | 51 | Ctrl/Cmd + I:: Auto indent current request. 52 | Ctrl + Space:: Open Auto complete (even if not typing). 53 | Ctrl/Cmd + Enter:: Submit request. 54 | Ctrl/Cmd + Up/Down:: Jump to the previous/next request start or end. 55 | Ctrl/Cmd + Alt + L:: Collapse/expand current scope. 56 | Ctrl/Cmd + Option + 0:: Collapse all scopes but the current one. Expand by adding a shift. 57 | 58 | ==== When auto-complete is visible 59 | 60 | Down arrow:: Switch focus to auto-complete menu. Use arrows to further select a term. 61 | Enter/Tab:: Select the currently selected or the top most term in auto-complete menu. 62 | Esc:: Close auto-complete menu. 63 | 64 | 65 | === History 66 | 67 | Sense maintains a list of the last 500 requests that were successfully executed by Elasticsearch. The history 68 | is available by clicking the clock icon on the top right side of the window. The icons opens the history panel 69 | where you can see the old requests. You can also select a request here and it will be added to the editor at 70 | the current cursor position. 71 | 72 | .History Panel 73 | image::images/history.png["History Panel"] 74 | 75 | 76 | === Settings 77 | 78 | Sense has multiple settings you can set. All of them are available in the Settings panel. To open the panel 79 | click on the cog icon on the top right. 80 | 81 | .Settings Panel 82 | image::images/settings.png["Setting Panel"] 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { ProxyConfigCollection } from './server/proxy_config_collection'; 2 | 3 | module.exports = function (kibana) { 4 | let { resolve, join, sep } = require('path'); 5 | let Joi = require('joi'); 6 | let Boom = require('boom'); 7 | let modules = resolve(__dirname, 'public/webpackShims/'); 8 | let src = resolve(__dirname, 'public/src/'); 9 | let { existsSync } = require('fs'); 10 | const { startsWith, endsWith } = require('lodash'); 11 | 12 | const apps = [ 13 | { 14 | title: 'Sense', 15 | description: 'JSON aware developer\'s interface to ElasticSearch', 16 | icon: 'plugins/sense/bonsai.png', 17 | main: 'plugins/sense/sense', 18 | injectVars: function (server, options) { 19 | return options; 20 | } 21 | } 22 | ]; 23 | 24 | if (existsSync(resolve(__dirname, 'public/tests'))) { 25 | apps.push({ 26 | title: 'Sense Tests', 27 | id: 'sense-tests', 28 | main: 'plugins/sense/tests', 29 | hidden: true 30 | //listed: false // uncomment after https://github.com/elastic/kibana/pull/4755 31 | }); 32 | } 33 | 34 | return new kibana.Plugin({ 35 | id: 'sense', 36 | 37 | config: function (Joi) { 38 | return Joi.object({ 39 | enabled: Joi.boolean().default(true), 40 | defaultServerUrl: Joi.string().default('http://localhost:9200'), 41 | proxyFilter: Joi.array().items(Joi.string()).single().default(['.*']), 42 | ssl: Joi.object({ 43 | verify: Joi.boolean(), 44 | }).default(), 45 | proxyConfig: Joi.array().items( 46 | Joi.object().keys({ 47 | match: Joi.object().keys({ 48 | protocol: Joi.string().default('*'), 49 | host: Joi.string().default('*'), 50 | port: Joi.string().default('*'), 51 | path: Joi.string().default('*') 52 | }), 53 | 54 | timeout: Joi.number(), 55 | ssl: Joi.object().keys({ 56 | verify: Joi.boolean(), 57 | ca: Joi.array().single().items(Joi.string()), 58 | cert: Joi.string(), 59 | key: Joi.string() 60 | }).default() 61 | }) 62 | ).default([ 63 | { 64 | match: { 65 | protocol: '*', 66 | host: '*', 67 | port: '*', 68 | path: '*' 69 | }, 70 | 71 | timeout: 180000, 72 | ssl: { 73 | verify: true 74 | } 75 | } 76 | ]) 77 | }).default(); 78 | }, 79 | 80 | init: function (server, options) { 81 | const filters = options.proxyFilter.map(str => new RegExp(str)); 82 | 83 | if (options.ssl && options.ssl.verify) { 84 | throw new Error('sense.ssl.version is no longer supported.'); 85 | } 86 | 87 | const proxyConfigCollection = new ProxyConfigCollection(options.proxyConfig); 88 | const proxyRouteConfig = { 89 | validate: { 90 | query: Joi.object().keys({ 91 | uri: Joi.string().uri({ 92 | allowRelative: false, 93 | shema: ['http:', 'https:'], 94 | }), 95 | }).unknown(true), 96 | }, 97 | 98 | pre: [ 99 | function filterUri(req, reply) { 100 | const { uri } = req.query; 101 | 102 | if (!filters.some(re => re.test(uri))) { 103 | const err = Boom.forbidden(); 104 | err.output.payload = "Error connecting to '" + uri + "':\n\nUnable to send requests to that url."; 105 | err.output.headers['content-type'] = 'text/plain'; 106 | reply(err); 107 | } else { 108 | reply(); 109 | } 110 | } 111 | ], 112 | 113 | handler(req, reply) { 114 | const { uri } = req.query; 115 | 116 | reply.proxy({ 117 | uri, 118 | xforward: true, 119 | passThrough: true, 120 | onResponse(err, res, request, reply, settings, ttl) { 121 | if (err != null) { 122 | reply("Error connecting to '" + request.query.uri + "':\n\n" + err.message).type("text/plain").statusCode = 502; 123 | } else { 124 | reply(null, res); 125 | } 126 | }, 127 | 128 | ...proxyConfigCollection.configForUri(uri) 129 | }) 130 | } 131 | }; 132 | 133 | server.route({ 134 | path: '/api/sense/proxy', 135 | method: '*', 136 | config: { 137 | ...proxyRouteConfig, 138 | 139 | payload: { 140 | output: 'stream', 141 | parse: false 142 | }, 143 | } 144 | }); 145 | 146 | server.route({ 147 | path: '/api/sense/proxy', 148 | method: 'GET', 149 | config: { 150 | ...proxyRouteConfig 151 | } 152 | }) 153 | 154 | server.route({ 155 | path: '/api/sense/api_server', 156 | method: ['GET', 'POST'], 157 | handler: function (req, reply) { 158 | let server = require('./api_server/server'); 159 | let {sense_version, apis} = req.query; 160 | if (!apis) { 161 | reply(Boom.badRequest('"apis" is a required param.')); 162 | return; 163 | } 164 | 165 | return server.resolveApi(sense_version, apis.split(","), reply); 166 | } 167 | }); 168 | 169 | const testApp = kibana.uiExports.apps.hidden.byId['sense-tests']; 170 | if (testApp) { 171 | server.route({ 172 | path: '/app/sense-tests', 173 | method: 'GET', 174 | handler: function (req, reply) { 175 | return reply.renderApp(testApp); 176 | } 177 | }); 178 | } 179 | }, 180 | 181 | uiExports: { 182 | apps: apps, 183 | 184 | noParse: [ 185 | join(modules, 'ace' + sep), 186 | join(modules, 'moment_src/moment' + sep), 187 | join(src, 'sense_editor/mode/worker.js') 188 | ] 189 | } 190 | }) 191 | }; 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Boaz Leskes ", 3 | "contributors": [ 4 | "Spencer Alger " 5 | ], 6 | "name": "sense", 7 | "version": "2.0.0-snapshot", 8 | "repository": { 9 | "type": "git", 10 | "url": "http://github.com/elastic/sense.git" 11 | }, 12 | "scripts": { 13 | "build": "grunt build", 14 | "release": "grunt release", 15 | "lint": "grunt eslint:source", 16 | "setup_kibana": "grunt setup_kibana", 17 | "test": "npm run test:server", 18 | "test:server": "plugin-helpers test:server" 19 | }, 20 | "devDependencies": { 21 | "@elastic/plugin-helpers": "^5.0.0-beta1", 22 | "babel-eslint": "^4.1.6", 23 | "eslint": "^1.7.3", 24 | "eslint-config-airbnb": "^0.1.0", 25 | "eslint-plugin-react": "^3.6.3", 26 | "expect.js": "^0.3.1", 27 | "grunt": "^0.4.5", 28 | "grunt-aws": "^0.6.1", 29 | "grunt-cli": "^0.1.13", 30 | "grunt-contrib-clean": "^0.6.0", 31 | "grunt-contrib-compress": "^0.14.0", 32 | "grunt-contrib-copy": "^0.8.2", 33 | "grunt-gitinfo": "^0.1.7", 34 | "grunt-replace": "^0.11.0", 35 | "grunt-run": "^0.5.2", 36 | "gruntify-eslint": "^1.2.0", 37 | "jit-grunt": "^0.9.1", 38 | "mocha": "^2.4.5", 39 | "sinon": "^1.17.3" 40 | }, 41 | "dependencies": { 42 | "boom": "2.8.0", 43 | "joi": "6.6.1", 44 | "lodash": "3.10.1", 45 | "minimatch": "^3.0.0" 46 | }, 47 | "license": "Apache-2.0" 48 | } 49 | -------------------------------------------------------------------------------- /public/bonsai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/public/bonsai.png -------------------------------------------------------------------------------- /public/css/sense.less: -------------------------------------------------------------------------------- 1 | @import "./sense.light.css"; 2 | @import (reference) "~ui/styles/variables"; 3 | 4 | nav.navbar .logo.hidden-sm { 5 | display: block !important; 6 | } 7 | 8 | [sense-navbar] { 9 | .server-url-form { 10 | flex: 1 1 1%; 11 | } 12 | } 13 | 14 | #editor_output_container { 15 | display: flex; 16 | flex: 1 1 auto; 17 | position: relative; 18 | padding: 10px; 19 | } 20 | 21 | #editor_container { 22 | display: flex; 23 | flex: 0 0 auto; 24 | width: 468px; 25 | position: relative; 26 | 27 | #autocomplete { 28 | position: absolute; 29 | left: -1000px; 30 | } 31 | 32 | #editor_actions { 33 | position: absolute; 34 | } 35 | 36 | #editor { 37 | flex: 1 1 1px; 38 | } 39 | } 40 | 41 | #editor_resizer { 42 | display: flex; 43 | flex: 0 0 13px; 44 | cursor: ew-resize; 45 | background-color: transparent; 46 | align-items: center; 47 | 48 | &:hover { 49 | background-color: #DCDCDC; 50 | } 51 | 52 | &.active { 53 | background-color: rgba(194, 193, 208, 100); 54 | } 55 | } 56 | 57 | #output_container { 58 | display: flex; 59 | flex: 1 1 1px; 60 | 61 | #output { 62 | flex: 1 1 1px; 63 | } 64 | } 65 | 66 | #editor_actions { 67 | position: absolute; 68 | top: 0; 69 | right: 0; 70 | line-height: 1; 71 | padding: 1px 3px 0 0; 72 | /* by pass any other element in ace and resize bar, but not modal popups */ 73 | z-index: 1005; 74 | 75 | .editor_action { 76 | padding: 2px 4px; 77 | &:hover { 78 | text-decoration: none; 79 | } 80 | } 81 | 82 | .fa-wrench { 83 | color: rgb(51, 51, 51); 84 | } 85 | 86 | .fa-play { 87 | color: rgb(112, 186, 86); 88 | } 89 | } 90 | 91 | .ace_gutter { 92 | margin-top: 1px; 93 | } 94 | 95 | #autocomplete { 96 | visibility: hidden; 97 | position: absolute; 98 | /* by pass any other element in ace and resize bar, but not modal popups */ 99 | z-index: 1003; 100 | margin-top: 22px; 101 | /*font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;*/ 102 | /*font-size: 12px;*/ 103 | /*max-height: 100px;*/ 104 | /*overflow-y: auto;*/ 105 | /* prevent horizontal scrollbar */ 106 | /*overflow-x: hidden;*/ 107 | } 108 | 109 | .ui-state-focus { 110 | margin: auto !important; 111 | } 112 | 113 | sense-help-example { 114 | display: block; 115 | height: 11em; 116 | margin: 1em 0; 117 | } 118 | 119 | .history-body { 120 | display: flex; 121 | height: 300px; 122 | 123 | .history-reqs, 124 | .history-viewer { 125 | flex: 0 0 50%; 126 | } 127 | 128 | .history-reqs { 129 | overflow: auto; 130 | margin: 0 25px 0 0; 131 | } 132 | 133 | .history-req { 134 | display: flex; 135 | 136 | &.selected { 137 | background-color: #DCDCDC; 138 | } 139 | 140 | .history-req-icon { 141 | flex: 1 0 auto; 142 | text-align: right; 143 | .fa-chevron-right { 144 | opacity: .25; 145 | } 146 | } 147 | } 148 | } 149 | 150 | .history-footer { 151 | text-align: right; 152 | padding-top: 10px; 153 | } 154 | 155 | .ui-autocomplete { 156 | z-index: 400 !important; 157 | } 158 | 159 | .ace_editor { 160 | line-height: normal; 161 | } 162 | 163 | .ace_layer.ace_marker-layer { 164 | overflow: visible; 165 | } 166 | 167 | .ace_snippet-marker { 168 | position: absolute; 169 | width: 100% !important; 170 | } 171 | 172 | // .ui-resizable-e { 173 | // cursor: ew-resize; 174 | // width: 13px; 175 | // right: -14px; 176 | // top: -1px; 177 | // bottom: -2px; 178 | // background-color: transparent; 179 | // position: absolute; 180 | // } 181 | // 182 | // .ui-resizable-e:hover { 183 | // background-color: #DCDCDC; 184 | // } 185 | 186 | .ui-resizable-e.active { 187 | background-color: rgba(194, 193, 208, 100); 188 | } 189 | 190 | /** COPIED FROM BOOTSTRAP, MODIFIED SELECTORS **/ 191 | .dropdown-menu li > a.zeroclipboard-is-hover, 192 | .dropdown-menu li > a.zeroclipboard-is-active { 193 | color: #FFF; 194 | text-decoration: none; 195 | background-color: #0081C2; 196 | background-image: -moz-linear-gradient(top, #08c, #0077b3); 197 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08C), to(#0077B3)); 198 | background-image: -webkit-linear-gradient(top, #08C, #0077B3); 199 | background-image: -o-linear-gradient(top, #08c, #0077b3); 200 | background-image: linear-gradient(to bottom, #08C, #0077B3); 201 | background-repeat: repeat-x; 202 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); 203 | } 204 | 205 | /** END COPIED **/ 206 | -------------------------------------------------------------------------------- /public/css/sense.light.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #f7f7f7; 4 | } 5 | 6 | #editor, #output { 7 | outline: 1px solid #ccc; 8 | border-top: 1px solid #ffffff; 9 | } 10 | 11 | .ace_url { 12 | color: #0088cc; 13 | } 14 | 15 | .ace_url.ace_param { 16 | color: rgb(49, 132, 149); 17 | } 18 | 19 | .ace_url.ace_value { 20 | color: rgb(3, 106, 7); 21 | } 22 | 23 | .ace-tm .ace_gutter { 24 | background: transparent; 25 | } 26 | 27 | .ace_scroller { 28 | border-left: 1px solid #ccc; 29 | } 30 | 31 | .ace_method { 32 | color: rgb(197, 6, 11); 33 | } 34 | 35 | .ace_snippet-marker { 36 | background: rgba(194, 193, 208, 0.20); 37 | border-top: dotted 1px rgba(194, 193, 208, 0.80); 38 | border-bottom: dotted 1px rgba(194, 193, 208, 0.80); 39 | margin-top: -1px; /* compensate for background */ 40 | } 41 | 42 | 43 | .ui-menu { 44 | background-color: white; 45 | border: 1px solid #CCC 46 | } 47 | 48 | .ui-state-focus { 49 | color: white; 50 | background-color: #08C; 51 | } 52 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/public/favicon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/public/icon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 |
    10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 |
    31 | 32 |
    GET _search 33 | { 34 | "query": { "match_all": {} } 35 | }
    36 | 37 |
    38 |
    39 |
    40 |
    {}
    41 |
    42 |
    43 | -------------------------------------------------------------------------------- /public/sense.js: -------------------------------------------------------------------------------- 1 | require('ace'); 2 | require('ui-bootstrap-custom'); 3 | 4 | require('ui/modules').get('kibana', ['sense.ui.bootstrap']); 5 | require('ui/tooltip'); 6 | require('ui/autoload/styles'); 7 | 8 | require('./css/sense.less'); 9 | require('./src/controllers/SenseController'); 10 | require('./src/directives/senseHistory'); 11 | require('./src/directives/senseSettings'); 12 | require('./src/directives/senseHelp'); 13 | require('./src/directives/senseWelcome'); 14 | require('./src/directives/senseNavbar'); 15 | 16 | require('ui/chrome') 17 | .setBrand({ 18 | logo: 'url(../plugins/sense/icon.png) center no-repeat' 19 | }) 20 | .setRootTemplate(require('./index.html')) 21 | .setRootController('sense', 'SenseController'); 22 | -------------------------------------------------------------------------------- /public/src/app.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | let curl = require('./curl'); 4 | let history = require('./history'); 5 | let input = require('./input'); 6 | let mappings = require('./mappings'); 7 | let output = require('./output'); 8 | let es = require('./es'); 9 | let utils = require('./utils'); 10 | let _ = require('lodash'); 11 | const chrome = require('ui/chrome'); 12 | 13 | const defaultServerUrl = chrome.getInjected('defaultServerUrl'); 14 | 15 | $(document.body).removeClass('fouc'); 16 | 17 | // set the value of the server and/or the input and clear the output 18 | function resetToValues(server, content) { 19 | if (server != null) { 20 | es.setBaseUrl(server); 21 | } 22 | if (content != null) { 23 | input.update(content); 24 | } 25 | output.update(""); 26 | } 27 | 28 | function loadSavedState() { 29 | var sourceLocation = utils.getUrlParam('load_from') || "stored"; 30 | var previousSaveState = history.getSavedEditorState(); 31 | 32 | if (sourceLocation == "stored") { 33 | if (previousSaveState) { 34 | resetToValues(previousSaveState.server, previousSaveState.content); 35 | } 36 | else { 37 | resetToValues(defaultServerUrl); 38 | input.autoIndent(); 39 | } 40 | } 41 | else if (/^https?:\/\//.test(sourceLocation)) { 42 | var loadFrom = {url: sourceLocation, dataType: "text", kbnXsrfToken: false}; 43 | if (/https?:\/\/api.github.com/.test(sourceLocation)) { 44 | loadFrom.headers = {Accept: "application/vnd.github.v3.raw"}; 45 | } 46 | $.ajax(loadFrom).done(function (data) { 47 | resetToValues(defaultServerUrl, data); 48 | input.moveToNextRequestEdge(true); 49 | input.highlightCurrentRequestsAndUpdateActionBar(); 50 | input.updateActionsBar(); 51 | }); 52 | } 53 | else if (previousSaveState) { 54 | resetToValues(previousSaveState.server); 55 | } 56 | else { 57 | resetToValues(defaultServerUrl); 58 | } 59 | input.moveToNextRequestEdge(true); 60 | } 61 | 62 | function setupAutosave() { 63 | var timer; 64 | var saveDelay = 500; 65 | 66 | input.getSession().on("change", function onChange(e) { 67 | if (timer) { 68 | timer = clearTimeout(timer); 69 | } 70 | timer = setTimeout(saveCurrentState, saveDelay); 71 | }); 72 | 73 | es.addServerChangeListener(saveCurrentState); 74 | } 75 | 76 | function saveCurrentState() { 77 | try { 78 | var content = input.getValue(); 79 | var server = es.getBaseUrl(); 80 | history.updateCurrentState(server, content); 81 | } 82 | catch (e) { 83 | console.log("Ignoring saving error: " + e); 84 | } 85 | } 86 | 87 | // stupid simple restore function, called when the user 88 | // chooses to restore a request from the history 89 | // PREVENTS history from needing to know about the input 90 | history.restoreFromHistory = function applyHistoryElem(req) { 91 | var session = input.getSession(); 92 | var pos = input.getCursorPosition(); 93 | var prefix = ""; 94 | var suffix = "\n"; 95 | if (input.parser.isStartRequestRow(pos.row)) { 96 | pos.column = 0; 97 | suffix += "\n"; 98 | } 99 | else if (input.parser.isEndRequestRow(pos.row)) { 100 | var line = session.getLine(pos.row); 101 | pos.column = line.length; 102 | prefix = "\n\n"; 103 | } 104 | else if (input.parser.isInBetweenRequestsRow(pos.row)) { 105 | pos.column = 0; 106 | } 107 | else { 108 | pos = input.nextRequestEnd(pos); 109 | prefix = "\n\n"; 110 | } 111 | 112 | var s = prefix + req.method + " " + req.endpoint; 113 | if (req.data) { 114 | s += "\n" + req.data; 115 | } 116 | 117 | s += suffix; 118 | 119 | session.insert(pos, s); 120 | input.clearSelection(); 121 | input.moveCursorTo(pos.row + prefix.length, 0); 122 | input.focus(); 123 | }; 124 | 125 | function resize() { 126 | input.resize(); 127 | output.resize(); 128 | } 129 | 130 | resize(); 131 | $(window).resize((event) => { 132 | if (event.target === window) resize(); 133 | }); 134 | 135 | loadSavedState(); 136 | setupAutosave(); 137 | -------------------------------------------------------------------------------- /public/src/autocomplete/url_params.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let engine = require('./engine'); 3 | 4 | function ParamComponent(name, parent, description) { 5 | engine.ConstantComponent.call(this, name, parent); 6 | this.description = description; 7 | } 8 | 9 | ParamComponent.prototype = _.create(engine.ConstantComponent.prototype, {"constructor": ParamComponent}); 10 | module.exports.ParamComponent = ParamComponent; 11 | 12 | (function (cls) { 13 | cls.getTerms = function () { 14 | var t = {name: this.name}; 15 | if (this.description === "__flag__") { 16 | t.meta = "flag" 17 | } 18 | else { 19 | t.meta = "param"; 20 | t.insert_value = this.name + "="; 21 | } 22 | return [t]; 23 | }; 24 | 25 | })(ParamComponent.prototype); 26 | 27 | function UrlParams(description, defaults) { 28 | // This is not really a component, just a handy container to make iteration logic simpler 29 | this.rootComponent = new engine.SharedComponent("ROOT"); 30 | if (_.isUndefined(defaults)) { 31 | defaults = { 32 | "pretty": "__flag__", 33 | "format": ["json", "yaml"], 34 | "filter_path": "", 35 | }; 36 | } 37 | description = _.clone(description || {}); 38 | _.defaults(description, defaults); 39 | _.each(description, function (p_description, param) { 40 | var values, component; 41 | component = new ParamComponent(param, this.rootComponent, p_description); 42 | if (_.isArray(p_description)) { 43 | values = new engine.ListComponent(param, p_description, component); 44 | } 45 | else if (p_description === "__flag__") { 46 | values = new engine.ListComponent(param, ["true", "false"], component); 47 | } 48 | }, this); 49 | 50 | } 51 | 52 | (function (cls) { 53 | 54 | cls.getTopLevelComponents = function () { 55 | return this.rootComponent.next; 56 | } 57 | 58 | })(UrlParams.prototype); 59 | 60 | module.exports.UrlParams = UrlParams; 61 | -------------------------------------------------------------------------------- /public/src/autocomplete/url_pattern_matcher.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let engine = require('./engine'); 3 | 4 | module.exports.URL_PATH_END_MARKER = "__url_path_end__"; 5 | 6 | 7 | function AcceptEndpointComponent(endpoint, parent) { 8 | engine.SharedComponent.call(this, endpoint.id, parent); 9 | this.endpoint = endpoint 10 | } 11 | 12 | AcceptEndpointComponent.prototype = _.create(engine.SharedComponent.prototype, {"constructor": AcceptEndpointComponent}); 13 | 14 | (function (cls) { 15 | 16 | cls.match = function (token, context, editor) { 17 | if (token !== module.exports.URL_PATH_END_MARKER) { 18 | return null; 19 | } 20 | if (this.endpoint.methods && -1 === _.indexOf(this.endpoint.methods, context.method)) { 21 | return null; 22 | } 23 | var r = Object.getPrototypeOf(cls).match.call(this, token, context, editor); 24 | r.context_values = r.context_values || {}; 25 | r.context_values['endpoint'] = this.endpoint; 26 | if (_.isNumber(this.endpoint.priority)) { 27 | r.priority = this.endpoint.priority; 28 | } 29 | return r; 30 | } 31 | })(AcceptEndpointComponent.prototype); 32 | 33 | 34 | /** 35 | * @param parametrizedComponentFactories a dict of the following structure 36 | * that will be used as a fall back for pattern parameters (i.e.: {indices}) 37 | * { 38 | * indices: function (part, parent) { 39 | * return new SharedComponent(part, parent) 40 | * } 41 | * } 42 | * @constructor 43 | */ 44 | function UrlPatternMatcher(parametrizedComponentFactories) { 45 | // This is not really a component, just a handy container to make iteration logic simpler 46 | this.rootComponent = new engine.SharedComponent("ROOT"); 47 | this.parametrizedComponentFactories = parametrizedComponentFactories || {}; 48 | } 49 | 50 | (function (cls) { 51 | cls.addEndpoint = function (pattern, endpoint) { 52 | var c, 53 | active_component = this.rootComponent, 54 | endpointComponents = endpoint.url_components || {}; 55 | var partList = pattern.split("/"); 56 | _.each(partList, function (part, partIndex) { 57 | if (part.search(/^{.+}$/) >= 0) { 58 | part = part.substr(1, part.length - 2); 59 | if (active_component.getComponent(part)) { 60 | // we already have something for this, reuse 61 | active_component = active_component.getComponent(part); 62 | return; 63 | } 64 | // a new path, resolve. 65 | 66 | if ((c = endpointComponents[part])) { 67 | // endpoint specific. Support list 68 | if (_.isArray(c)) { 69 | c = new engine.ListComponent(part, c, active_component); 70 | } 71 | else if (_.isObject(c) && c.type === "list") { 72 | c = new engine.ListComponent(part, c.list, active_component, c.multi_valued, c.allow_non_valid); 73 | } 74 | else { 75 | console.warn("incorrectly configured url component ", part, " in endpoint", endpoint); 76 | c = new engine.SharedComponent(part); 77 | } 78 | } 79 | else if ((c = this.parametrizedComponentFactories[part])) { 80 | // c is a f 81 | c = c(part, active_component); 82 | } 83 | else { 84 | // just accept whatever with not suggestions 85 | c = new engine.SimpleParamComponent(part, active_component); 86 | } 87 | 88 | active_component = c; 89 | } 90 | else { 91 | // not pattern 92 | var lookAhead = part, s; 93 | 94 | for (partIndex++; partIndex < partList.length; partIndex++) { 95 | s = partList[partIndex]; 96 | if (s.indexOf("{") >= 0) { 97 | break; 98 | } 99 | lookAhead += "/" + s; 100 | 101 | } 102 | 103 | if (active_component.getComponent(part)) { 104 | // we already have something for this, reuse 105 | active_component = active_component.getComponent(part); 106 | active_component.addOption(lookAhead); 107 | } 108 | else { 109 | c = new engine.ConstantComponent(part, active_component, lookAhead); 110 | active_component = c; 111 | } 112 | } 113 | }, this); 114 | // mark end of endpoint path 115 | new AcceptEndpointComponent(endpoint, active_component); 116 | }; 117 | 118 | cls.getTopLevelComponents = function () { 119 | return this.rootComponent.next; 120 | } 121 | 122 | })(UrlPatternMatcher.prototype); 123 | 124 | module.exports.UrlPatternMatcher = UrlPatternMatcher; 125 | -------------------------------------------------------------------------------- /public/src/controllers/SenseController.js: -------------------------------------------------------------------------------- 1 | require('ui/doc_title'); 2 | 3 | 4 | require('ui/modules') 5 | .get('app/sense') 6 | .controller('SenseController', function SenseController($scope, docTitle) { 7 | 8 | docTitle.change('Sense'); 9 | 10 | // require the root app code, which expects to execute once the dom is loaded up 11 | require('../app'); 12 | 13 | const ConfigTemplate = require('ui/config_template'); 14 | const input = require('../input'); 15 | const es = require('../es'); 16 | const storage = require('../storage'); 17 | 18 | this.dropdown = new ConfigTemplate({ 19 | welcome: '', 20 | history: '', 21 | settings: '', 22 | help: '', 23 | }); 24 | 25 | /** 26 | * Display the welcome dropdown if it has not been shown yet 27 | */ 28 | if (storage.get('version_welcome_shown') !== '@@SENSE_REVISION') { 29 | this.dropdown.open('welcome'); 30 | } 31 | 32 | this.sendSelected = () => { 33 | input.focus(); 34 | input.sendCurrentRequestToES(); 35 | return false; 36 | }; 37 | 38 | this.autoIndent = (event) => { 39 | input.autoIndent(); 40 | event.preventDefault(); 41 | input.focus(); 42 | }; 43 | 44 | this.serverUrl = es.getBaseUrl(); 45 | 46 | // read server url changes into scope 47 | es.addServerChangeListener((server) => { 48 | $scope.$evalAsync(() => { 49 | this.serverUrl = server; 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /public/src/curl.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 'use strict'; 3 | 4 | function detectCURLinLine(line) { 5 | // returns true if text matches a curl request 6 | return line.match(/^\s*?curl\s+(-X[A-Z]+)?\s*['"]?.*?['"]?(\s*$|\s+?-d\s*?['"])/); 7 | 8 | } 9 | 10 | function detectCURL(text) { 11 | // returns true if text matches a curl request 12 | if (!text) return false; 13 | for (var line of text.split("\n")) { 14 | if (detectCURLinLine(line)) { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | 21 | function parseCURL(text) { 22 | var state = 'NONE'; 23 | var out = []; 24 | var body = []; 25 | var line = ''; 26 | var lines = text.trim().split("\n"); 27 | var matches; 28 | 29 | var EmptyLine = /^\s*$/; 30 | var Comment = /^\s*(?:#|\/{2,})(.*)\n?$/; 31 | var ExecutionComment = /^\s*#!/; 32 | var ClosingSingleQuote = /^([^']*)'/; 33 | var ClosingDoubleQuote = /^((?:[^\\"]|\\.)*)"/; 34 | var EscapedQuotes = /^((?:[^\\"']|\\.)+)/; 35 | 36 | var LooksLikeCurl = /^\s*curl\s+/; 37 | var CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/; 38 | 39 | var HasProtocol = /[\s"']https?:\/\//; 40 | var CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/; 41 | var CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/; 42 | var CurlData = /^.+\s(--data|-d)\s*/; 43 | var SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/; 44 | 45 | if (lines.length > 0 && ExecutionComment.test(lines[0])) { 46 | lines.shift(); 47 | } 48 | 49 | function nextLine() { 50 | if (line.length > 0) { 51 | return true; 52 | } 53 | if (lines.length == 0) { 54 | return false; 55 | } 56 | line = lines.shift().replace(/[\r\n]+/g, "\n") + "\n"; 57 | return true; 58 | } 59 | 60 | function unescapeLastBodyEl() { 61 | var str = body.pop().replace(/\\([\\"'])/g, "$1"); 62 | body.push(str); 63 | } 64 | 65 | // Is the next char a single or double quote? 66 | // If so remove it 67 | function detectQuote() { 68 | if (line.substr(0, 1) == "'") { 69 | line = line.substr(1); 70 | state = 'SINGLE_QUOTE'; 71 | } 72 | else if (line.substr(0, 1) == '"') { 73 | line = line.substr(1); 74 | state = 'DOUBLE_QUOTE'; 75 | } 76 | else { 77 | state = 'UNQUOTED'; 78 | } 79 | } 80 | 81 | // Body is finished - append to output with final LF 82 | function addBodyToOut() { 83 | if (body.length > 0) { 84 | out.push(body.join("")); 85 | body = []; 86 | } 87 | state = 'LF'; 88 | out.push("\n"); 89 | } 90 | 91 | // If the pattern matches, then the state is about to change, 92 | // so add the capture to the body and detect the next state 93 | // Otherwise add the whole line 94 | function consumeMatching(pattern) { 95 | var matches = line.match(pattern); 96 | if (matches) { 97 | body.push(matches[1]); 98 | line = line.substr(matches[0].length); 99 | detectQuote(); 100 | } 101 | else { 102 | body.push(line); 103 | line = ''; 104 | } 105 | } 106 | 107 | function parseCurlLine() { 108 | var verb = 'GET'; 109 | var request = ''; 110 | var matches; 111 | if (matches = line.match(CurlVerb)) { 112 | verb = matches[1]; 113 | } 114 | 115 | // JS regexen don't support possesive quantifiers, so 116 | // we need two distinct patterns 117 | var pattern = HasProtocol.test(line) 118 | ? CurlRequestWithProto 119 | : CurlRequestWithoutProto; 120 | 121 | if (matches = line.match(pattern)) { 122 | request = matches[1]; 123 | } 124 | 125 | out.push(verb + ' /' + request + "\n"); 126 | 127 | if (matches = line.match(CurlData)) { 128 | line = line.substr(matches[0].length); 129 | detectQuote(); 130 | if (EmptyLine.test(line)) { 131 | line = ''; 132 | } 133 | } 134 | else { 135 | state = 'NONE'; 136 | line = ''; 137 | out.push(''); 138 | } 139 | } 140 | 141 | while (nextLine()) { 142 | 143 | if (state == 'SINGLE_QUOTE') { 144 | consumeMatching(ClosingSingleQuote); 145 | } 146 | 147 | else if (state == 'DOUBLE_QUOTE') { 148 | consumeMatching(ClosingDoubleQuote); 149 | unescapeLastBodyEl(); 150 | } 151 | 152 | else if (state == 'UNQUOTED') { 153 | consumeMatching(EscapedQuotes); 154 | if (body.length) { 155 | unescapeLastBodyEl(); 156 | } 157 | if (state == 'UNQUOTED') { 158 | addBodyToOut(); 159 | line = '' 160 | } 161 | } 162 | 163 | // the BODY state (used to match the body of a Sense request) 164 | // can be terminated early if it encounters 165 | // a comment or an empty line 166 | else if (state == 'BODY') { 167 | if (Comment.test(line) || EmptyLine.test(line)) { 168 | addBodyToOut(); 169 | } 170 | else { 171 | body.push(line); 172 | line = ''; 173 | } 174 | } 175 | 176 | else if (EmptyLine.test(line)) { 177 | if (state != 'LF') { 178 | out.push("\n"); 179 | state = 'LF'; 180 | } 181 | line = ''; 182 | } 183 | 184 | else if (matches = line.match(Comment)) { 185 | out.push("#" + matches[1] + "\n"); 186 | state = 'NONE'; 187 | line = ''; 188 | } 189 | 190 | else if (LooksLikeCurl.test(line)) { 191 | parseCurlLine(); 192 | } 193 | 194 | else if (matches = line.match(SenseLine)) { 195 | out.push(matches[1] + ' /' + matches[2] + "\n"); 196 | line = ''; 197 | state = 'BODY'; 198 | } 199 | 200 | // Nothing else matches, so output with a prefix of !!! for debugging purposes 201 | else { 202 | out.push('### ' + line); 203 | line = ''; 204 | } 205 | } 206 | 207 | addBodyToOut(); 208 | return out.join('').trim(); 209 | } 210 | 211 | 212 | return { 213 | parseCURL: parseCURL, 214 | detectCURL: detectCURL 215 | }; 216 | 217 | }); 218 | -------------------------------------------------------------------------------- /public/src/directives/help.html: -------------------------------------------------------------------------------- 1 |

    Help

    2 | 3 | 4 | 5 | You can type one or more requests in the white editor. Sense understands requests in a compact format: 6 | 7 | 8 | 9 |
    10 |
    11 |
    General editing
    12 |
    Ctrl/Cmd + I
    13 |
    Auto indent current request
    14 |
    Ctrl + Space
    15 |
    Open Auto complete (even if not typing)
    16 |
    Ctrl/Cmd + Enter
    17 |
    Submit request
    18 |
    Ctrl/Cmd + Up/Down
    19 |
    Jump to the previous/next request start or end.
    20 |
    Ctrl/Cmd + Alt + L
    21 |
    Collapse/expand current scope.
    22 |
    Ctrl/Cmd + Option + 0
    23 |
    Collapse all scopes but the current one. Expand by adding a shift.
    24 |
    25 |
    26 |
    27 |
    When auto-complete is visible
    28 |
    Down arrow
    29 |
    Switch focus to auto-complete menu. Use arrows to further select a term
    30 |
    Enter/Tab
    31 |
    Select the currently selected or the top most term in auto-complete menu
    32 |
    Esc
    33 |
    Close auto-complete menu
    34 |
    35 |
    36 |
    37 | -------------------------------------------------------------------------------- /public/src/directives/helpExample.txt: -------------------------------------------------------------------------------- 1 | # index a doc 2 | PUT index/type/1 3 | { 4 | "body": "here" 5 | } 6 | 7 | # and get it ... 8 | GET index/type/1 9 | -------------------------------------------------------------------------------- /public/src/directives/history.html: -------------------------------------------------------------------------------- 1 |

    History

    2 | 3 |
    4 |
      5 |
    • 15 | 16 | {{ history.describeReq(req) }} 17 | 18 |
    • 19 |
    20 | 21 | 22 |
    23 | 24 | 33 | -------------------------------------------------------------------------------- /public/src/directives/navbar.html: -------------------------------------------------------------------------------- 1 | Server 2 | 3 |
    8 | 9 | 23 |
    24 | 25 | 62 | -------------------------------------------------------------------------------- /public/src/directives/senseHelp.js: -------------------------------------------------------------------------------- 1 | require('./senseHelpExample'); 2 | 3 | require('ui/modules') 4 | .get('app/sense') 5 | .directive('senseHelp', function () { 6 | return { 7 | restrict: 'E', 8 | template: require('./help.html') 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /public/src/directives/senseHelpExample.js: -------------------------------------------------------------------------------- 1 | const SenseEditor = require('../sense_editor/editor'); 2 | const exampleText = require('raw!./helpExample.txt').trim(); 3 | 4 | require('ui/modules') 5 | .get('app/sense') 6 | .directive('senseHelpExample', function () { 7 | return { 8 | restrict: 'E', 9 | link: function ($scope, $el) { 10 | $el.text(exampleText); 11 | $scope.editor = new SenseEditor($el); 12 | $scope.editor.setReadOnly(true); 13 | $scope.editor.$blockScrolling = Infinity; 14 | 15 | $scope.$on('$destroy', function () { 16 | if ($scope.editor) $scope.editor.destroy(); 17 | }); 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /public/src/directives/senseHistory.js: -------------------------------------------------------------------------------- 1 | var { assign, memoize } = require('lodash'); 2 | let moment = require('moment'); 3 | 4 | var history = require('../history'); 5 | require('./senseHistoryViewer'); 6 | 7 | require('ui/modules') 8 | .get('app/sense') 9 | .directive('senseHistory', function () { 10 | return { 11 | restrict: 'E', 12 | template: require('./history.html'), 13 | controllerAs: 'history', 14 | controller: function ($scope, $element) { 15 | this.reqs = history.getHistory(); 16 | this.selectedReq = this.reqs[0]; 17 | this.viewingReq = this.selectedReq; 18 | 19 | // calculate the text description of a request 20 | this.describeReq = memoize((req) => { 21 | const endpoint = req.endpoint; 22 | const date = moment(req.time); 23 | 24 | let formattedDate = date.format("MMM D"); 25 | if (date.diff(moment(), "days") > -7) { 26 | formattedDate = date.fromNow(); 27 | } 28 | 29 | return `${endpoint} (${formattedDate})`; 30 | }); 31 | this.describeReq.cache = new WeakMap(); 32 | 33 | // main actions 34 | this.clear = () => { 35 | history.clearHistory($element); 36 | $scope.close(); 37 | }; 38 | 39 | this.restore = (req = this.selectedReq) => { 40 | history.restoreFromHistory(req); 41 | $scope.close(); 42 | }; 43 | } 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /public/src/directives/senseHistoryViewer.js: -------------------------------------------------------------------------------- 1 | const history = require('../history'); 2 | let SenseEditor = require('../sense_editor/editor'); 3 | 4 | require('ui/modules') 5 | .get('app/sense') 6 | .directive('senseHistoryViewer', function () { 7 | return { 8 | restrict: 'E', 9 | scope: { 10 | req: '=', 11 | }, 12 | link: function ($scope, $el) { 13 | const viewer = new SenseEditor($el); 14 | viewer.setReadOnly(true); 15 | viewer.renderer.setShowPrintMargin(false); 16 | require('../settings').applyCurrentSettings(viewer); 17 | 18 | $scope.$watch('req', function (req) { 19 | if (req) { 20 | var s = req.method + " " + req.endpoint + "\n" + (req.data || ""); 21 | viewer.setValue(s); 22 | viewer.clearSelection(); 23 | } else { 24 | viewer.getSession().setValue("No history available") 25 | } 26 | }); 27 | 28 | $scope.$on('$destroy', function () { 29 | viewer.destroy(); 30 | }); 31 | } 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /public/src/directives/senseNavbar.js: -------------------------------------------------------------------------------- 1 | const history = require('../history'); 2 | const es = require('../es'); 3 | 4 | require('ui/modules') 5 | .get('app/sense') 6 | .directive('senseNavbar', function () { 7 | return { 8 | restrict: 'A', 9 | template: require('./navbar.html'), 10 | controllerAs: 'navbar', 11 | controller: function SenseNavbarController($scope, $element) { 12 | this.serverUrlHistory = []; 13 | this.updateServerUrlHistory = function () { 14 | this.serverUrlHistory = history.getHistoricalServers(); 15 | }; 16 | 17 | this.updateServerUrlHistory(); 18 | 19 | this.commitServerUrlFormModel = () => { 20 | es.setBaseUrl(this.serverUrlFormModel); 21 | }; 22 | 23 | $scope.$watch('sense.serverUrl', (serverUrl) => { 24 | this.serverUrlFormModel = serverUrl; 25 | }); 26 | } 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /public/src/directives/senseSettings.js: -------------------------------------------------------------------------------- 1 | require('ui/directives/input_focus'); 2 | 3 | require('ui/modules') 4 | .get('app/sense') 5 | .directive('senseSettings', function () { 6 | return { 7 | restrict: 'E', 8 | template: require('./settings.html'), 9 | controllerAs: 'settings', 10 | controller: function ($scope) { 11 | const settings = require('../settings'); 12 | 13 | this.vals = settings.getCurrentSettings(); 14 | this.apply = () => { 15 | this.vals = settings.updateSettings(this.vals); 16 | $scope.close(); 17 | }; 18 | 19 | }, 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /public/src/directives/senseWelcome.js: -------------------------------------------------------------------------------- 1 | require('./senseHelpExample'); 2 | 3 | const storage = require('../storage'); 4 | 5 | require('ui/modules') 6 | .get('app/sense') 7 | .directive('senseWelcome', function () { 8 | return { 9 | restrict: 'E', 10 | template: require('./welcome.html'), 11 | link: function ($scope) { 12 | // date junk is a work around for https://github.com/elastic/kibana/pull/5167 13 | var shown = Date.now(); 14 | 15 | $scope.$on('$destroy', function () { 16 | if (Date.now() - shown > 1000) { 17 | storage.set('version_welcome_shown', '@@SENSE_REVISION'); 18 | } 19 | }); 20 | } 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /public/src/directives/settings.html: -------------------------------------------------------------------------------- 1 |

    Settings

    2 | 3 |
    4 |
    5 |

    Editor & Output pane settings

    6 | 7 |
    8 | 9 |
    10 | 18 |
    19 |
    20 | 21 |
    22 | 25 |
    26 | 27 |
    28 | 29 |
    30 |

    Autocomplete

    31 | 32 |
    33 | 41 |
    42 | 43 |
    44 | 52 |
    53 | 54 |
    55 | 56 | 69 |
    70 | -------------------------------------------------------------------------------- /public/src/directives/welcome.html: -------------------------------------------------------------------------------- 1 |

    Welcome to Sense

    2 | 3 |

    Quick intro to the UI

    4 | 5 |

    Sense is split into two panes: an editor pane (left) and a response pane (right). 6 | Use the editor to type requests and submit them to Elasticsearch. The results will be displayed in 7 | the response pane on the right side. 8 |

    9 | 10 |

    Sense understands requests in a compact format, similar to cURL: 11 | 12 | 13 |

    While typing a request, Sense will make suggestions which you can than accept by hitting Enter/Tab. 14 | These suggestions are made based on the request structure as well as your indices and types. 15 |

    16 | 17 | 18 |

    A few quick tips, while I have your attention

    19 |
      20 |
    • Submit requests to ES using the green triangle button.
    • 21 |
    • Use the wrench menu for other useful things.
    • 22 |
    • You can paste requests in cURL format and they will be translated to the Sense syntax.
    • 23 |
    • You can resize the editor and output panes by dragging the separator between them.
    • 24 |
    • Study the keyboard shortcuts under the Help button. Good stuff in there!
    • 25 |
    26 | 27 | 28 | -------------------------------------------------------------------------------- /public/src/es.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let $ = require('jquery'); 3 | 4 | let baseUrl; 5 | let serverChangeListeners = []; 6 | let esVersion = []; 7 | 8 | module.exports.getBaseUrl = function () { 9 | return baseUrl; 10 | }; 11 | module.exports.getVersion = function () { 12 | return esVersion; 13 | }; 14 | 15 | module.exports.send = function (method, path, data, server, disable_auth_alert) { 16 | var wrappedDfd = $.Deferred(); 17 | 18 | server = server || exports.getBaseUrl(); 19 | path = exports.constructESUrl(server, path); 20 | var uname_password_re = /^(https?:\/\/)?(?:(?:([^\/]*):)?([^\/]*?)@)?(.*)$/; 21 | var url_parts = path.match(uname_password_re); 22 | 23 | var uname = url_parts[2]; 24 | var password = url_parts[3]; 25 | path = url_parts[1] + url_parts[4]; 26 | console.log("Calling " + path + " (uname: " + uname + " pwd: " + password + ")"); 27 | if (data && method == "GET") { 28 | method = "POST"; 29 | } 30 | 31 | // delayed loading for circular references 32 | var settings = require("./settings"); 33 | 34 | var options = { 35 | url: '../api/sense/proxy?uri=' + encodeURIComponent(path), 36 | data: method == "GET" ? null : data, 37 | cache: false, 38 | crossDomain: true, 39 | type: method, 40 | password: password, 41 | username: uname, 42 | dataType: "text", // disable automatic guessing 43 | }; 44 | 45 | 46 | $.ajax(options).then( 47 | function (data, textStatus, jqXHR) { 48 | wrappedDfd.resolveWith(this, [data, textStatus, jqXHR]); 49 | }, 50 | function (jqXHR, textStatus, errorThrown) { 51 | if (jqXHR.status == 0) { 52 | jqXHR.responseText = "\n\nFailed to connect to Sense's backend.\nPlease check the Kibana server is up and running"; 53 | } 54 | wrappedDfd.rejectWith(this, [jqXHR, textStatus, errorThrown]); 55 | }); 56 | return wrappedDfd; 57 | }; 58 | 59 | module.exports.constructESUrl = function (server, path) { 60 | if (!path) { 61 | path = server; 62 | server = exports.getBaseUrl(); 63 | } 64 | if (path.indexOf("://") >= 0) { 65 | return path; 66 | } 67 | if (server.indexOf("://") < 0) { 68 | server = (document.location.protocol || "http:") + "//" + server; 69 | } 70 | if (server.substr(-1) == "/") { 71 | server = server.substr(0, server.length - 1); 72 | } 73 | if (path.charAt(0) === "/") { 74 | path = path.substr(1); 75 | } 76 | 77 | return server + "/" + path; 78 | }; 79 | 80 | module.exports.forceRefresh = function () { 81 | exports.setBaseUrl(baseUrl, true) 82 | }; 83 | 84 | module.exports.setBaseUrl = function (base, force) { 85 | if (baseUrl !== base || force) { 86 | var old = baseUrl; 87 | baseUrl = base; 88 | exports.send("GET", "/").done(function (data, status, xhr) { 89 | if (xhr.status === 200) { 90 | // parse for version 91 | var value = xhr.responseText; 92 | try { 93 | value = JSON.parse(value); 94 | if (value.version && value.version.number) { 95 | esVersion = value.version.number.split("."); 96 | } 97 | } 98 | catch (e) { 99 | 100 | } 101 | } 102 | _.each(serverChangeListeners, function (cb) { 103 | cb(base, old) 104 | }); 105 | }).fail(function () { 106 | esVersion = []; // unknown 107 | _.each(serverChangeListeners, function (cb) { 108 | cb(base, old) 109 | }); 110 | }); 111 | } 112 | }; 113 | 114 | module.exports.addServerChangeListener = function (cb) { 115 | serverChangeListeners.push(cb); 116 | }; 117 | -------------------------------------------------------------------------------- /public/src/history.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | const { uniq } = require('lodash'); 3 | const storage = require('./storage'); 4 | const chrome = require('ui/chrome'); 5 | 6 | const defaultServerUrl = chrome.getInjected('defaultServerUrl'); 7 | 8 | const history = module.exports = { 9 | restoreFromHistory() { 10 | // default method for history.restoreFromHistory 11 | // replace externally to do something when the user chooses 12 | // to relive a bit of history 13 | throw new Error('not implemented'); 14 | }, 15 | 16 | getHistoryKeys() { 17 | return storage.keys() 18 | .filter(key => key.indexOf('hist_elem') === 0) 19 | .sort() 20 | .reverse(); 21 | }, 22 | 23 | getHistory() { 24 | return history 25 | .getHistoryKeys() 26 | .map(key => storage.get(key)); 27 | }, 28 | 29 | getHistoricalServers() { 30 | return uniq(history.getHistory().map(req => req.server)); 31 | }, 32 | 33 | addToHistory(server, endpoint, method, data) { 34 | var keys = history.getHistoryKeys(); 35 | keys.splice(0, 500); // only maintain most recent X; 36 | $.each(keys, function (i, k) { 37 | storage.delete(k); 38 | }); 39 | 40 | var timestamp = new Date().getTime(); 41 | var k = "hist_elem_" + timestamp; 42 | storage.set(k, { 43 | time: timestamp, 44 | server: server, 45 | endpoint: endpoint, 46 | method: method, 47 | data: data 48 | }); 49 | }, 50 | 51 | updateCurrentState(server, content) { 52 | var timestamp = new Date().getTime(); 53 | storage.set("editor_state", { 54 | time: timestamp, 55 | server: server === defaultServerUrl ? undefined : server, 56 | content: content 57 | }); 58 | }, 59 | 60 | getSavedEditorState() { 61 | const saved = storage.get('editor_state'); 62 | if (!saved) return; 63 | const { time, server = defaultServerUrl, content } = saved; 64 | return { time, server, content }; 65 | }, 66 | 67 | clearHistory($el) { 68 | history 69 | .getHistoryKeys() 70 | .forEach(key => storage.delete(key)); 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /public/src/input_resize.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | const storage = require('./storage'); 3 | 4 | module.exports = function (input, output) { 5 | 6 | const $left = input.$el.parent(); 7 | 8 | function readStoredEditorWidth() { 9 | return storage.get('editorWidth'); 10 | } 11 | 12 | function storeEditorWidth(editorWidth) { 13 | storage.set('editorWidth', editorWidth); 14 | } 15 | 16 | function setEditorWidth(editorWidth) { 17 | storeEditorWidth(editorWidth); 18 | $left.width(editorWidth); 19 | } 20 | 21 | var $resizer = $('#editor_resizer'); 22 | $resizer 23 | .on('mousedown', function (event) { 24 | $resizer.addClass('active'); 25 | var startWidth = $left.width(); 26 | var startX = event.pageX; 27 | 28 | function onMove(event) { 29 | setEditorWidth(startWidth + event.pageX - startX) 30 | } 31 | 32 | $(document.body) 33 | .on('mousemove', onMove) 34 | .one('mouseup', function () { 35 | $resizer.removeClass('active'); 36 | $(this).off('mousemove', onMove); 37 | input.resize(); 38 | output.resize(); 39 | }); 40 | }); 41 | 42 | const initialEditorWidth = readStoredEditorWidth(); 43 | if (initialEditorWidth != null) { 44 | setEditorWidth(initialEditorWidth); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /public/src/kb/api.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let url_pattern_matcher = require('../autocomplete/url_pattern_matcher'); 3 | let url_params = require('../autocomplete/url_params'); 4 | let body_completer = require('../autocomplete/body_completer'); 5 | 6 | /** 7 | * 8 | * @param urlParametrizedComponentFactories a dictionary of factory functions 9 | * that will be used as fallback for parametrized path part (i.e., {indices} ) 10 | * see url_pattern_matcher.UrlPatternMatcher 11 | * @constructor 12 | * @param bodyParametrizedComponentFactories same as urlParametrizedComponentFactories but used for body compilation 13 | */ 14 | function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactories) { 15 | this.globalRules = {}; 16 | this.endpoints = {}; 17 | this.urlPatternMatcher = new url_pattern_matcher.UrlPatternMatcher(urlParametrizedComponentFactories); 18 | this.globalBodyComponentFactories = bodyParametrizedComponentFactories; 19 | this.name = ""; 20 | } 21 | 22 | (function (cls) { 23 | cls.addGlobalAutocompleteRules = function (parentNode, rules) { 24 | this.globalRules[parentNode] = body_completer.compileBodyDescription( 25 | "GLOBAL." + parentNode, rules, this.globalBodyComponentFactories); 26 | }; 27 | 28 | cls.getGlobalAutocompleteComponents = function (term, throwOnMissing) { 29 | var result = this.globalRules[term]; 30 | if (_.isUndefined(result) && (throwOnMissing || _.isUndefined(throwOnMissing))) { 31 | throw new Error("failed to resolve global components for ['" + term + "']"); 32 | } 33 | return result; 34 | }; 35 | 36 | cls.addEndpointDescription = function (endpoint, description) { 37 | 38 | var copiedDescription = {}; 39 | _.extend(copiedDescription, description || {}); 40 | _.defaults(copiedDescription, { 41 | id: endpoint, 42 | patterns: [endpoint], 43 | methods: ['GET'] 44 | }); 45 | _.each(copiedDescription.patterns, function (p) { 46 | this.urlPatternMatcher.addEndpoint(p, copiedDescription); 47 | }, this); 48 | 49 | copiedDescription.paramsAutocomplete = new url_params.UrlParams(copiedDescription.url_params); 50 | copiedDescription.bodyAutocompleteRootComponents = body_completer.compileBodyDescription( 51 | copiedDescription.id, copiedDescription.data_autocomplete_rules, this.globalBodyComponentFactories); 52 | 53 | this.endpoints[endpoint] = copiedDescription; 54 | }; 55 | 56 | cls.getEndpointDescriptionByEndpoint = function (endpoint) { 57 | return this.endpoints[endpoint]; 58 | }; 59 | 60 | 61 | cls.getTopLevelUrlCompleteComponents = function () { 62 | return this.urlPatternMatcher.getTopLevelComponents(); 63 | }; 64 | 65 | cls.getUnmatchedEndpointComponents = function () { 66 | return body_completer.globalsOnlyAutocompleteComponents(); 67 | }; 68 | 69 | cls.clear = function () { 70 | this.endpoints = {}; 71 | this.globalRules = {}; 72 | }; 73 | }(Api.prototype)); 74 | 75 | 76 | module.exports = Api; 77 | -------------------------------------------------------------------------------- /public/src/output.js: -------------------------------------------------------------------------------- 1 | let ace = require('ace'); 2 | let $ = require('jquery'); 3 | let settings = require('./settings'); 4 | let OutputMode = require('./sense_editor/mode/output'); 5 | const smartResize = require('./smart_resize'); 6 | 7 | var $el = $("#output"); 8 | var output = ace.require('ace/ace').edit($el[0]); 9 | 10 | var outputMode = new OutputMode.Mode(); 11 | 12 | output.resize = smartResize(output); 13 | output.update = function (val, mode, cb) { 14 | if (typeof mode === 'function') { 15 | cb = mode; 16 | mode = void 0; 17 | } 18 | 19 | var session = output.getSession(); 20 | 21 | session.setMode(val ? (mode || outputMode) : 'ace/mode/text'); 22 | session.setValue(val); 23 | if (typeof cb === 'function') { 24 | setTimeout(cb); 25 | } 26 | }; 27 | 28 | output.append = function (val, fold_previous, cb) { 29 | if (typeof fold_previous === 'function') { 30 | cb = fold_previous; 31 | fold_previous = true; 32 | } 33 | if (_.isUndefined(fold_previous)) { 34 | fold_previous = true; 35 | } 36 | var session = output.getSession(); 37 | var lastLine = session.getLength(); 38 | if (fold_previous) { 39 | output.moveCursorTo(Math.max(0, lastLine - 1), 0); 40 | session.toggleFold(false); 41 | 42 | } 43 | session.insert({row: lastLine, column: 0}, "\n" + val); 44 | output.moveCursorTo(lastLine + 1, 0); 45 | if (typeof cb === 'function') { 46 | setTimeout(cb); 47 | } 48 | }; 49 | 50 | output.$el = $el; 51 | 52 | (function (session) { 53 | session.setMode("ace/mode/text"); 54 | session.setFoldStyle('markbeginend'); 55 | session.setTabSize(2); 56 | session.setUseWrapMode(true); 57 | }(output.getSession())); 58 | 59 | output.setShowPrintMargin(false); 60 | output.setReadOnly(true); 61 | 62 | if (settings) { 63 | settings.applyCurrentSettings(output); 64 | } 65 | 66 | module.exports = output; 67 | -------------------------------------------------------------------------------- /public/src/sense_editor/mode/input.js: -------------------------------------------------------------------------------- 1 | let ace = require('ace'); 2 | let acequire = require('acequire'); 3 | let mode_json = require('ace/mode-json'); 4 | 5 | var oop = acequire("ace/lib/oop"); 6 | var TextMode = acequire("ace/mode/text").Mode; 7 | var MatchingBraceOutdent = acequire("ace/mode/matching_brace_outdent").MatchingBraceOutdent; 8 | var CstyleBehaviour = acequire("ace/mode/behaviour/cstyle").CstyleBehaviour; 9 | var CStyleFoldMode = acequire("ace/mode/folding/cstyle").FoldMode; 10 | var WorkerClient = acequire("ace/worker/worker_client").WorkerClient; 11 | var AceTokenizer = acequire("ace/tokenizer").Tokenizer; 12 | 13 | var HighlightRules = require("./input_highlight_rules").InputHighlightRules; 14 | 15 | acequire("ace/config").setModuleUrl("sense_editor/mode/worker", require("file!./worker.js")); 16 | 17 | 18 | var Mode = function () { 19 | this.$tokenizer = new AceTokenizer(new HighlightRules().getRules()); 20 | this.$outdent = new MatchingBraceOutdent(); 21 | this.$behaviour = new CstyleBehaviour(); 22 | this.foldingRules = new CStyleFoldMode(); 23 | }; 24 | oop.inherits(Mode, TextMode); 25 | 26 | (function () { 27 | this.getCompletions = function (editor, session, pos, prefix) { 28 | // autocomplete is done by the autocomplete module. 29 | return []; 30 | }; 31 | 32 | this.getNextLineIndent = function (state, line, tab) { 33 | var indent = this.$getIndent(line); 34 | 35 | if (state != "double_q_string") { 36 | var match = line.match(/^.*[\{\(\[]\s*$/); 37 | if (match) { 38 | indent += tab; 39 | } 40 | } 41 | 42 | return indent; 43 | }; 44 | 45 | this.checkOutdent = function (state, line, input) { 46 | return this.$outdent.checkOutdent(line, input); 47 | }; 48 | 49 | this.autoOutdent = function (state, doc, row) { 50 | this.$outdent.autoOutdent(doc, row); 51 | }; 52 | 53 | this.createWorker = function (session) { 54 | var worker = new WorkerClient(["ace", "sense_editor"], "sense_editor/mode/worker", "SenseWorker"); 55 | worker.attachToDocument(session.getDocument()); 56 | 57 | 58 | worker.on("error", function (e) { 59 | session.setAnnotations([e.data]); 60 | }); 61 | 62 | worker.on("ok", function (anno) { 63 | session.setAnnotations(anno.data); 64 | }); 65 | 66 | return worker; 67 | }; 68 | 69 | 70 | }).call(Mode.prototype); 71 | 72 | module.exports.Mode = Mode; 73 | -------------------------------------------------------------------------------- /public/src/sense_editor/mode/input_highlight_rules.js: -------------------------------------------------------------------------------- 1 | let ace = require('ace'); 2 | 3 | var oop = ace.require("ace/lib/oop"); 4 | var TextHighlightRules = ace.require("ace/mode/text_highlight_rules").TextHighlightRules; 5 | 6 | var InputHighlightRules = function () { 7 | 8 | function mergeTokens(/* ... */) { 9 | return [].concat.apply([], arguments); 10 | } 11 | 12 | function addEOL(tokens, reg, nextIfEOL, normalNext) { 13 | if (typeof reg == "object") { 14 | reg = reg.source; 15 | } 16 | return [ 17 | {token: tokens.concat(["whitespace"]), regex: reg + "(\\s*)$", next: nextIfEOL}, 18 | {token: tokens, regex: reg, next: normalNext} 19 | ]; 20 | } 21 | 22 | // regexp must not have capturing parentheses. Use (?:) instead. 23 | // regexps are ordered -> the first match is used 24 | /*jshint -W015 */ 25 | this.$rules = { 26 | "start": mergeTokens([ 27 | {token: "comment", regex: /^#.*$/}, 28 | {token: "paren.lparen", regex: "{", next: "json", push: true} 29 | ], 30 | addEOL(["method"], /([a-zA-Z]+)/, "start", "method_sep") 31 | , 32 | [ 33 | { 34 | token: "whitespace", 35 | regex: "\\s+" 36 | }, 37 | { 38 | token: "text", 39 | regex: ".+?" 40 | } 41 | ]), 42 | "method_sep": mergeTokens( 43 | addEOL(["whitespace", "url.protocol_host", "url.slash"], /(\s+)(https?:\/\/[^?\/,]+)(\/)/, "start", "url"), 44 | addEOL(["whitespace", "url.protocol_host"], /(\s+)(https?:\/\/[^?\/,]+)/, "start", "url"), 45 | addEOL(["whitespace", "url.slash"], /(\s+)(\/)/, "start", "url"), 46 | addEOL(["whitespace"], /(\s+)/, "start", "url") 47 | ), 48 | "url": mergeTokens( 49 | addEOL(["url.part"], /([^?\/,\s]+)/, "start"), 50 | addEOL(["url.comma"], /(,)/, "start"), 51 | addEOL(["url.slash"], /(\/)/, "start"), 52 | addEOL(["url.questionmark"], /(\?)/, "start", "urlParams") 53 | ), 54 | "urlParams": mergeTokens( 55 | addEOL(["url.param", "url.equal", "url.value"], /([^&=]+)(=)([^&]*)/, "start"), 56 | addEOL(["url.param"], /([^&=]+)/, "start"), 57 | addEOL(["url.amp"], /(&)/, "start") 58 | ), 59 | 60 | 61 | "json": [ 62 | { 63 | token: "variable", // single line 64 | regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)' 65 | }, 66 | { 67 | token: "string", // single line 68 | regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' 69 | }, 70 | { 71 | token: "constant.numeric", // hex 72 | regex: "0[xX][0-9a-fA-F]+\\b" 73 | }, 74 | { 75 | token: "constant.numeric", // float 76 | regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" 77 | }, 78 | { 79 | token: "constant.language.boolean", 80 | regex: "(?:true|false)\\b" 81 | }, 82 | { 83 | token: "invalid.illegal", // single quoted strings are not allowed 84 | regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" 85 | }, 86 | { 87 | token: "invalid.illegal", // comments are not allowed 88 | regex: "\\/\\/.*$" 89 | }, 90 | { 91 | token: "paren.lparen", 92 | merge: false, 93 | regex: "{", 94 | next: "json", 95 | push: true 96 | }, 97 | { 98 | token: "paren.lparen", 99 | merge: false, 100 | regex: "[[(]" 101 | }, 102 | { 103 | token: "paren.rparen", 104 | merge: false, 105 | regex: "[\\])]" 106 | }, 107 | { 108 | token: "paren.rparen", 109 | regex: "}", 110 | merge: false, 111 | next: "pop" 112 | }, 113 | { 114 | token: "punctuation.comma", 115 | regex: "," 116 | }, 117 | { 118 | token: "punctuation.colon", 119 | regex: ":" 120 | }, 121 | { 122 | token: "whitespace", 123 | regex: "\\s+" 124 | }, 125 | { 126 | token: "text", 127 | regex: ".+?" 128 | } 129 | ], 130 | "double_q_string": [ 131 | { 132 | token: "string", 133 | regex: '[^"]+' 134 | }, 135 | { 136 | token: "punctuation.end_quote", 137 | regex: '"', 138 | next: "json" 139 | }, 140 | { 141 | token: "string", 142 | regex: "", 143 | next: "json" 144 | } 145 | ] 146 | }; 147 | 148 | if (this.constructor === InputHighlightRules) { 149 | this.normalizeRules(); 150 | } 151 | }; 152 | 153 | oop.inherits(InputHighlightRules, TextHighlightRules); 154 | 155 | module.exports.InputHighlightRules = InputHighlightRules; 156 | -------------------------------------------------------------------------------- /public/src/sense_editor/mode/output.js: -------------------------------------------------------------------------------- 1 | let ace = require('ace'); 2 | let acequire = require('acequire'); 3 | let mode_json = require('ace/mode-json'); 4 | let output_highlighting_rules = require('./output_highlight_rules'); 5 | 6 | 7 | var oop = ace.require("ace/lib/oop"); 8 | var JSONMode = ace.require("ace/mode/json").Mode; 9 | var HighlightRules = require("./output_highlight_rules").OutputJsonHighlightRules; 10 | var MatchingBraceOutdent = ace.require("ace/mode/matching_brace_outdent").MatchingBraceOutdent; 11 | var CstyleBehaviour = ace.require("ace/mode/behaviour/cstyle").CstyleBehaviour; 12 | var CStyleFoldMode = ace.require("ace/mode/folding/cstyle").FoldMode; 13 | var WorkerClient = ace.require("ace/worker/worker_client").WorkerClient; 14 | var AceTokenizer = ace.require("ace/tokenizer").Tokenizer; 15 | 16 | var Mode = function () { 17 | this.$tokenizer = new AceTokenizer(new HighlightRules().getRules()); 18 | this.$outdent = new MatchingBraceOutdent(); 19 | this.$behaviour = new CstyleBehaviour(); 20 | this.foldingRules = new CStyleFoldMode(); 21 | }; 22 | oop.inherits(Mode, JSONMode); 23 | 24 | (function () { 25 | this.createWorker = function (session) { 26 | return null; 27 | }; 28 | 29 | this.$id = "sense/mode/input"; 30 | }).call(Mode.prototype); 31 | 32 | module.exports.Mode = Mode; 33 | -------------------------------------------------------------------------------- /public/src/sense_editor/mode/output_highlight_rules.js: -------------------------------------------------------------------------------- 1 | let ace = require('ace'); 2 | let ace_mode_json = require('ace/mode-json'); 3 | 4 | var oop = ace.require("ace/lib/oop"); 5 | var JsonHighlightRules = ace.require("ace/mode/json_highlight_rules").JsonHighlightRules; 6 | 7 | var OutputJsonHighlightRules = function () { 8 | 9 | // regexp must not have capturing parentheses. Use (?:) instead. 10 | // regexps are ordered -> the first match is used 11 | this.$rules = new JsonHighlightRules().getRules(); 12 | 13 | this.$rules.start.unshift( 14 | { 15 | "token": "comment", 16 | "regex": "#.*$" 17 | } 18 | ); 19 | 20 | }; 21 | 22 | oop.inherits(OutputJsonHighlightRules, JsonHighlightRules); 23 | 24 | module.exports.OutputJsonHighlightRules = OutputJsonHighlightRules; 25 | -------------------------------------------------------------------------------- /public/src/sense_editor/row_parser.js: -------------------------------------------------------------------------------- 1 | let MODE = { 2 | REQUEST_START: 2, 3 | IN_REQUEST: 4, 4 | MULTI_DOC_CUR_DOC_END: 8, 5 | REQUEST_END: 16, 6 | BETWEEN_REQUESTS: 32 7 | 8 | }; 9 | 10 | function RowParser(editor) { 11 | var defaultEditor = editor; 12 | 13 | this.getRowParseMode = function (row) { 14 | if (row == null || typeof row == "undefined") { 15 | row = editor.getCursorPosition().row; 16 | } 17 | 18 | var session = editor.getSession(); 19 | if (row >= session.getLength() || row < 0) { 20 | return MODE.BETWEEN_REQUESTS; 21 | } 22 | var mode = session.getState(row); 23 | if (!mode) { 24 | return MODE.BETWEEN_REQUESTS; 25 | } // shouldn't really happen 26 | 27 | if (mode !== "start") { 28 | return MODE.IN_REQUEST; 29 | } 30 | var line = (session.getLine(row) || "").trim(); 31 | if (!line || line[0] === '#') { 32 | return MODE.BETWEEN_REQUESTS; 33 | } // empty line or a comment waiting for a new req to start 34 | 35 | if (line.indexOf("}", line.length - 1) >= 0) { 36 | // check for a multi doc request (must start a new json doc immediately after this one end. 37 | row++; 38 | if (row < session.getLength()) { 39 | line = (session.getLine(row) || "").trim(); 40 | if (line.indexOf("{") === 0) { // next line is another doc in a multi doc 41 | return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST; 42 | } 43 | 44 | } 45 | return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request 46 | } 47 | 48 | // check for single line requests 49 | row++; 50 | if (row >= session.getLength()) { 51 | return MODE.REQUEST_START | MODE.REQUEST_END; 52 | } 53 | line = (session.getLine(row) || "").trim(); 54 | if (line.indexOf("{") !== 0) { // next line is another request 55 | return MODE.REQUEST_START | MODE.REQUEST_END; 56 | } 57 | 58 | return MODE.REQUEST_START; 59 | }; 60 | 61 | this.rowPredicate = function (row, editor, value) { 62 | var mode = this.getRowParseMode(row, editor); 63 | return (mode & value) > 0; 64 | }; 65 | 66 | this.isEndRequestRow = function (row, _e) { 67 | var editor = _e || defaultEditor; 68 | return this.rowPredicate(row, editor, MODE.REQUEST_END); 69 | }; 70 | 71 | this.isRequestEdge = function (row, _e) { 72 | var editor = _e || defaultEditor; 73 | return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START); 74 | }; 75 | 76 | this.isStartRequestRow = function (row, _e) { 77 | var editor = _e || defaultEditor; 78 | return this.rowPredicate(row, editor, MODE.REQUEST_START); 79 | }; 80 | 81 | this.isInBetweenRequestsRow = function (row, _e) { 82 | var editor = _e || defaultEditor; 83 | return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS); 84 | }; 85 | 86 | this.isInRequestsRow = function (row, _e) { 87 | var editor = _e || defaultEditor; 88 | return this.rowPredicate(row, editor, MODE.IN_REQUEST); 89 | }; 90 | 91 | this.isMultiDocDocEndRow = function (row, _e) { 92 | var editor = _e || defaultEditor; 93 | return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END); 94 | }; 95 | 96 | this.isEmptyToken = function (tokenOrTokenIter) { 97 | var token = tokenOrTokenIter && tokenOrTokenIter.getCurrentToken ? tokenOrTokenIter.getCurrentToken() : tokenOrTokenIter; 98 | return !token || token.type == "whitespace" 99 | }; 100 | 101 | this.isUrlOrMethodToken = function (tokenOrTokenIter) { 102 | var t = tokenOrTokenIter.getCurrentToken ? tokenOrTokenIter.getCurrentToken() : tokenOrTokenIter; 103 | return t && t.type && (t.type == "method" || t.type.indexOf("url") === 0); 104 | }; 105 | 106 | 107 | this.nextNonEmptyToken = function (tokenIter) { 108 | var t = tokenIter.stepForward(); 109 | while (t && this.isEmptyToken(t)) t = tokenIter.stepForward(); 110 | return t; 111 | }; 112 | 113 | this.prevNonEmptyToken = function (tokenIter) { 114 | var t = tokenIter.stepBackward(); 115 | // empty rows return null token. 116 | while ((t || tokenIter.getCurrentTokenRow() > 0) && this.isEmptyToken(t)) t = tokenIter.stepBackward(); 117 | return t; 118 | }; 119 | } 120 | 121 | RowParser.prototype.MODE = MODE; 122 | 123 | module.exports = RowParser; 124 | -------------------------------------------------------------------------------- /public/src/sense_editor/theme-sense-dark.js: -------------------------------------------------------------------------------- 1 | let ace = require('ace'); 2 | 3 | ace.define("ace/theme/sense-dark", ['require', 'exports', 'module'], 4 | function (require, exports, module) { 5 | exports.isDark = true; 6 | exports.cssClass = "ace-sense-dark"; 7 | exports.cssText = ".ace-sense-dark .ace_gutter {\ 8 | background: #2e3236;\ 9 | color: #bbbfc2;\ 10 | }\ 11 | .ace-sense-dark .ace_print-margin {\ 12 | width: 1px;\ 13 | background: #555651\ 14 | }\ 15 | .ace-sense-dark .ace_scroller {\ 16 | background-color: #202328;\ 17 | }\ 18 | .ace-sense-dark .ace_content {\ 19 | }\ 20 | .ace-sense-dark .ace_text-layer {\ 21 | color: #F8F8F2\ 22 | }\ 23 | .ace-sense-dark .ace_cursor {\ 24 | border-left: 2px solid #F8F8F0\ 25 | }\ 26 | .ace-sense-dark .ace_overwrite-cursors .ace_cursor {\ 27 | border-left: 0px;\ 28 | border-bottom: 1px solid #F8F8F0\ 29 | }\ 30 | .ace-sense-dark .ace_marker-layer .ace_selection {\ 31 | background: #222\ 32 | }\ 33 | .ace-sense-dark.ace_multiselect .ace_selection.ace_start {\ 34 | box-shadow: 0 0 3px 0px #272822;\ 35 | border-radius: 2px\ 36 | }\ 37 | .ace-sense-dark .ace_marker-layer .ace_step {\ 38 | background: rgb(102, 82, 0)\ 39 | }\ 40 | .ace-sense-dark .ace_marker-layer .ace_bracket {\ 41 | margin: -1px 0 0 -1px;\ 42 | border: 1px solid #49483E\ 43 | }\ 44 | .ace-sense-dark .ace_marker-layer .ace_active-line {\ 45 | background: #202020\ 46 | }\ 47 | .ace-sense-dark .ace_gutter-active-line {\ 48 | background-color: #272727\ 49 | }\ 50 | .ace-sense-dark .ace_marker-layer .ace_selected-word {\ 51 | border: 1px solid #49483E\ 52 | }\ 53 | .ace-sense-dark .ace_invisible {\ 54 | color: #49483E\ 55 | }\ 56 | .ace-sense-dark .ace_entity.ace_name.ace_tag,\ 57 | .ace-sense-dark .ace_keyword,\ 58 | .ace-sense-dark .ace_meta,\ 59 | .ace-sense-dark .ace_storage {\ 60 | color: #F92672\ 61 | }\ 62 | .ace-sense-dark .ace_constant.ace_character,\ 63 | .ace-sense-dark .ace_constant.ace_language,\ 64 | .ace-sense-dark .ace_constant.ace_numeric,\ 65 | .ace-sense-dark .ace_constant.ace_other {\ 66 | color: #AE81FF\ 67 | }\ 68 | .ace-sense-dark .ace_invalid {\ 69 | color: #F8F8F0;\ 70 | background-color: #F92672\ 71 | }\ 72 | .ace-sense-dark .ace_invalid.ace_deprecated {\ 73 | color: #F8F8F0;\ 74 | background-color: #AE81FF\ 75 | }\ 76 | .ace-sense-dark .ace_support.ace_constant,\ 77 | .ace-sense-dark .ace_support.ace_function {\ 78 | color: #66D9EF\ 79 | }\ 80 | .ace-sense-dark .ace_fold {\ 81 | background-color: #A6E22E;\ 82 | border-color: #F8F8F2\ 83 | }\ 84 | .ace-sense-dark .ace_storage.ace_type,\ 85 | .ace-sense-dark .ace_support.ace_class,\ 86 | .ace-sense-dark .ace_support.ace_type {\ 87 | font-style: italic;\ 88 | color: #66D9EF\ 89 | }\ 90 | .ace-sense-dark .ace_entity.ace_name.ace_function,\ 91 | .ace-sense-dark .ace_entity.ace_other.ace_attribute-name,\ 92 | .ace-sense-dark .ace_variable {\ 93 | color: #A6E22E\ 94 | }\ 95 | .ace-sense-dark .ace_variable.ace_parameter {\ 96 | font-style: italic;\ 97 | color: #FD971F\ 98 | }\ 99 | .ace-sense-dark .ace_string {\ 100 | color: #E6DB74\ 101 | }\ 102 | .ace-sense-dark .ace_comment {\ 103 | color: #629755\ 104 | }\ 105 | .ace-sense-dark .ace_markup.ace_underline {\ 106 | text-decoration: underline\ 107 | }\ 108 | .ace-sense-dark .ace_indent-guide {\ 109 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNQ11D6z7Bq1ar/ABCKBG6g04U2AAAAAElFTkSuQmCC) right repeat-y\ 110 | }"; 111 | 112 | var dom = require("ace/lib/dom"); 113 | dom.importCssString(exports.cssText, exports.cssClass); 114 | }); 115 | 116 | -------------------------------------------------------------------------------- /public/src/settings.js: -------------------------------------------------------------------------------- 1 | let $ = require('jquery'); 2 | let es = require('./es'); 3 | const storage = require('./storage'); 4 | 5 | function getFontSize() { 6 | return storage.get('font_size', 12); 7 | } 8 | 9 | function setFontSize(size) { 10 | storage.set('font_size', size); 11 | applyCurrentSettings(); 12 | return true; 13 | } 14 | 15 | function getWrapMode() { 16 | return storage.get('wrap_mode', true); 17 | } 18 | 19 | function setWrapMode(mode) { 20 | storage.set('wrap_mode', mode); 21 | applyCurrentSettings(); 22 | return true; 23 | } 24 | 25 | function setBasicAuth(mode) { 26 | storage.set('basic_auth', mode); 27 | applyCurrentSettings(); 28 | return true; 29 | } 30 | 31 | function getAutocomplete() { 32 | return storage.get('autocomplete_settings', { fields: true, indices: true }); 33 | } 34 | 35 | function setAutocomplete(settings) { 36 | storage.set('autocomplete_settings', settings); 37 | return true; 38 | } 39 | 40 | function applyCurrentSettings(editor) { 41 | if (typeof editor === 'undefined') { 42 | applyCurrentSettings(require('./input')); 43 | applyCurrentSettings(require('./output')); 44 | } 45 | if (editor) { 46 | editor.getSession().setUseWrapMode(getWrapMode()); 47 | editor.$el.css('font-size', getFontSize() + 'px'); 48 | } 49 | } 50 | 51 | function getCurrentSettings() { 52 | return { 53 | autocomplete: getAutocomplete(), 54 | wrapMode: getWrapMode(), 55 | fontSize: parseFloat(getFontSize()), 56 | }; 57 | } 58 | 59 | function updateSettings({ fontSize, wrapMode, autocomplete}) { 60 | setFontSize(fontSize); 61 | setWrapMode(wrapMode); 62 | setAutocomplete(autocomplete); 63 | require('./input').focus(); 64 | es.forceRefresh(); 65 | return getCurrentSettings(); 66 | } 67 | 68 | module.exports = { 69 | getAutocomplete, 70 | applyCurrentSettings, 71 | 72 | getCurrentSettings, 73 | updateSettings, 74 | }; 75 | -------------------------------------------------------------------------------- /public/src/smart_resize.js: -------------------------------------------------------------------------------- 1 | import { throttle } from 'lodash'; 2 | 3 | module.exports = function (editor) { 4 | let resize = editor.resize; 5 | let lastDemensions = []; 6 | let timeout = null; 7 | 8 | let checkDelay = 250; 9 | let stableChecks = 0; 10 | let maxStableChecks = 20; 11 | 12 | function check() { 13 | clearTimeout(timeout); 14 | 15 | const $container = editor.$el.parent(); 16 | const demensions = [$container.width(), $container.height()]; 17 | const [ width, height ] = demensions; 18 | const [ lastWidth, lastHieght ] = lastDemensions; 19 | lastDemensions = demensions; 20 | 21 | if (width !== lastWidth || height !== lastHieght) { 22 | resize.call(editor, true); 23 | stableChecks = 0; 24 | } else { 25 | stableChecks += 1; 26 | } 27 | 28 | if (stableChecks < maxStableChecks) { 29 | scheduleCheck(); 30 | } 31 | } 32 | 33 | function scheduleCheck() { 34 | if (!timeout) timeout = setTimeout(check, checkDelay); 35 | } 36 | 37 | function requestResize() { 38 | stableChecks = 0; 39 | scheduleCheck(); 40 | } 41 | 42 | return requestResize; 43 | }; 44 | -------------------------------------------------------------------------------- /public/src/storage.js: -------------------------------------------------------------------------------- 1 | const { transform, keys, startsWith } = require('lodash'); 2 | 3 | class Storage { 4 | constructor(engine, prefix) { 5 | this.engine = engine; 6 | this.prefix = prefix; 7 | } 8 | 9 | encode(val) { 10 | return JSON.stringify(val); 11 | } 12 | 13 | decode(val) { 14 | if (typeof val === 'string') { 15 | return JSON.parse(val); 16 | } 17 | } 18 | 19 | encodeKey(key) { 20 | return `${this.prefix}${key}`; 21 | } 22 | 23 | decodeKey(key) { 24 | if (startsWith(key, this.prefix)) { 25 | return `${key.slice(this.prefix.length)}`; 26 | } 27 | } 28 | 29 | set(key, val) { 30 | this.engine.setItem(this.encodeKey(key), this.encode(val)); 31 | return val; 32 | } 33 | 34 | has(key) { 35 | return this.engine.getItem(this.encodeKey(key)) != null; 36 | } 37 | 38 | get(key, _default) { 39 | if (this.has(key)) { 40 | return this.decode(this.engine.getItem(this.encodeKey(key))); 41 | } else { 42 | return _default; 43 | } 44 | } 45 | 46 | delete(key) { 47 | return this.engine.removeItem(this.encodeKey(key)); 48 | } 49 | 50 | keys() { 51 | return transform(keys(this.engine), (ours, key) => { 52 | const ourKey = this.decodeKey(key); 53 | if (ourKey != null) ours.push(ourKey); 54 | }); 55 | } 56 | } 57 | 58 | module.exports = new Storage(localStorage, 'sense:'); 59 | -------------------------------------------------------------------------------- /public/src/utils.js: -------------------------------------------------------------------------------- 1 | var utils = {}; 2 | 3 | utils.textFromRequest = function (request) { 4 | var data = request.data; 5 | if (typeof data != "string") { 6 | data = data.join("\n"); 7 | } 8 | return request.method + " " + request.url + "\n" + data; 9 | }; 10 | 11 | utils.getUrlParam = function (name) { 12 | name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); 13 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 14 | results = regex.exec(location.search); 15 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 16 | }; 17 | 18 | utils.jsonToString = function (data, indent) { 19 | return JSON.stringify(data, null, indent ? 2 : 0); 20 | }; 21 | 22 | utils.reformatData = function (data, indent) { 23 | var changed = false; 24 | var formatted_data = []; 25 | for (var i = 0; i < data.length; i++) { 26 | var cur_doc = data[i]; 27 | try { 28 | var new_doc = utils.jsonToString(JSON.parse(cur_doc), indent ? 2 : 0); 29 | changed = changed || new_doc != cur_doc; 30 | formatted_data.push(new_doc); 31 | } 32 | catch (e) { 33 | console.log(e); 34 | formatted_data.push(cur_doc); 35 | } 36 | } 37 | 38 | return { 39 | changed: changed, 40 | data: formatted_data 41 | }; 42 | }; 43 | 44 | 45 | module.exports = utils; 46 | -------------------------------------------------------------------------------- /public/tests/index.html: -------------------------------------------------------------------------------- 1 | 30 |
    31 |
    32 |
    33 |
    34 |
    35 |
    36 |
    37 | -------------------------------------------------------------------------------- /public/tests/src/curl_parsing_tests.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let curl = require('../../src/curl'); 3 | let curlTests = require('raw!./curl_parsing_tests.txt'); 4 | 5 | var {test, module, ok, fail, asyncTest, deepEqual, equal, start} = QUnit; 6 | 7 | module("CURL"); 8 | 9 | var notCURLS = [ 10 | 'sldhfsljfhs', 11 | 's;kdjfsldkfj curl -XDELETE ""', 12 | '{ "hello": 1 }' 13 | ]; 14 | 15 | 16 | _.each(notCURLS, function (notCURL, i) { 17 | test("cURL Detection - broken strings " + i, function () { 18 | ok(!curl.detectCURL(notCURL), "marked as curl while it wasn't:" + notCURL); 19 | }); 20 | }); 21 | 22 | _.each(curlTests.split(/^=+$/m), function (fixture) { 23 | if (fixture.trim() == "") { 24 | return; 25 | } 26 | fixture = fixture.split(/^-+$/m); 27 | var name = fixture[0].trim(), 28 | curlText = fixture[1], 29 | response = fixture[2].trim(); 30 | 31 | test("cURL Detection - " + name, function () { 32 | ok(curl.detectCURL(curlText), "marked as not curl while it was:" + curlText); 33 | var r = curl.parseCURL(curlText); 34 | equal(r, response); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /public/tests/src/curl_parsing_tests.txt: -------------------------------------------------------------------------------- 1 | ========== 2 | Curl 1 3 | ------------------------------------- 4 | curl -XPUT 'http://localhost:9200/twitter/tweet/1' -d '{ 5 | "user" : "kimchy", 6 | "post_date" : "2009-11-15T14:12:12", 7 | "message" : "trying out Elastic Search" 8 | }' 9 | ------------------------------------- 10 | PUT /twitter/tweet/1 11 | { 12 | "user" : "kimchy", 13 | "post_date" : "2009-11-15T14:12:12", 14 | "message" : "trying out Elastic Search" 15 | } 16 | ========== 17 | Curl 2 18 | ------------------------------------- 19 | curl -XGET "localhost/twitter/tweet/1?version=2" -d '{ 20 | "message" : "elasticsearch now has versioning support, double cool!" 21 | }' 22 | ------------------------------------- 23 | GET /twitter/tweet/1?version=2 24 | { 25 | "message" : "elasticsearch now has versioning support, double cool!" 26 | } 27 | =========== 28 | Curl 3 29 | ------------------------------------- 30 | curl -XPOST https://localhost/twitter/tweet/1?version=2 -d '{ 31 | "message" : "elasticsearch now has versioning support, double cool!" 32 | }' 33 | ------------------------------------- 34 | POST /twitter/tweet/1?version=2 35 | { 36 | "message" : "elasticsearch now has versioning support, double cool!" 37 | } 38 | ========= 39 | Curl 4 40 | ------------------------------------- 41 | curl -XPOST https://localhost/twitter 42 | ------------------------------------- 43 | POST /twitter 44 | ========== 45 | Curl 5 46 | ------------------------------------- 47 | curl -X POST https://localhost/twitter/ 48 | ------------------------------------- 49 | POST /twitter/ 50 | ============= 51 | Curl 6 52 | ------------------------------------- 53 | curl -s -XPOST localhost:9200/missing-test -d' 54 | { 55 | "mappings": { 56 | } 57 | }' 58 | ------------------------------------- 59 | POST /missing-test 60 | { 61 | "mappings": { 62 | } 63 | } 64 | ========================= 65 | Curl 7 66 | ------------------------------------- 67 | curl 'localhost:9200/missing-test/doc/_search?pretty' -d' 68 | { 69 | "query": { 70 | }, 71 | }' 72 | ------------------------------------- 73 | GET /missing-test/doc/_search?pretty 74 | { 75 | "query": { 76 | }, 77 | } 78 | =========================== 79 | Curl 8 80 | ------------------------------------- 81 | curl localhost:9200/ -d' 82 | { 83 | "query": { 84 | } 85 | }' 86 | ------------------------------------- 87 | GET / 88 | { 89 | "query": { 90 | } 91 | } 92 | ==================================== 93 | Curl Script 94 | ------------------------------------- 95 | #!bin/sh 96 | 97 | // test something 98 | curl 'localhost:9200/missing-test/doc/_search?pretty' -d' 99 | { 100 | "query": { 101 | }, 102 | }' 103 | 104 | 105 | curl -XPOST https://localhost/twitter 106 | 107 | #someother comments 108 | curl localhost:9200/ -d' 109 | { 110 | "query": { 111 | } 112 | }' 113 | 114 | 115 | ------------------- 116 | # test something 117 | GET /missing-test/doc/_search?pretty 118 | { 119 | "query": { 120 | }, 121 | } 122 | 123 | POST /twitter 124 | 125 | #someother comments 126 | GET / 127 | { 128 | "query": { 129 | } 130 | } 131 | ==================================== 132 | Curl with some text 133 | ------------------------------------- 134 | This is what I meant: 135 | 136 | curl 'localhost:9200/missing-test/doc/_search?' 137 | 138 | This, however, does work: 139 | curl 'localhost:9200/missing/doc/_search?' 140 | ------------------- 141 | ### This is what I meant: 142 | 143 | GET /missing-test/doc/_search? 144 | 145 | ### This, however, does work: 146 | GET /missing/doc/_search? 147 | -------------------------------------------------------------------------------- /public/tests/src/editor_input1.txt: -------------------------------------------------------------------------------- 1 | GET _search 2 | { 3 | "query": { "match_all": {} } 4 | } 5 | 6 | #preceeding comment 7 | GET _stats?level=shards 8 | 9 | #in between comment 10 | 11 | PUT index_1/type1/1 12 | { 13 | "f": 1 14 | } 15 | 16 | PUT index_1/type1/2 17 | { 18 | "f": 2 19 | } 20 | 21 | # comment 22 | 23 | 24 | GET index_1/type1/1/_source?_source_include=f 25 | 26 | DELETE index_2 27 | 28 | -------------------------------------------------------------------------------- /public/tests/src/kb_tests.js: -------------------------------------------------------------------------------- 1 | let kb = require('../../src/kb'); 2 | let mappings = require('../../src/mappings'); 3 | let autocomplete_engine = require('../../src/autocomplete/engine'); 4 | 5 | var {test, module, ok, fail, asyncTest, deepEqual, equal, start} = QUnit; 6 | 7 | module("Knowledge base", { 8 | setup: function () { 9 | mappings.clear(); 10 | kb.setActiveApi(kb._test.loadApisFromJson({})); 11 | }, 12 | teardown: function () { 13 | mappings.clear(); 14 | kb.setActiveApi(kb._test.loadApisFromJson({})); 15 | } 16 | }); 17 | 18 | var MAPPING = { 19 | "index1": { 20 | "type1.1": { 21 | "properties": { 22 | "field1.1.1": {"type": "string"}, 23 | "field1.1.2": {"type": "long"} 24 | } 25 | }, 26 | "type1.2": { 27 | "properties": {} 28 | } 29 | }, 30 | "index2": { 31 | "type2.1": { 32 | "properties": { 33 | "field2.1.1": {"type": "string"}, 34 | "field2.1.2": {"type": "string"} 35 | } 36 | } 37 | } 38 | }; 39 | 40 | function testUrlContext(tokenPath, otherTokenValues, expectedContext) { 41 | 42 | if (expectedContext.autoCompleteSet) { 43 | expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { 44 | if (_.isString(t)) { 45 | t = {name: t} 46 | } 47 | return t; 48 | }) 49 | } 50 | 51 | var context = {otherTokenValues: otherTokenValues}; 52 | autocomplete_engine.populateContext(tokenPath, context, null, 53 | expectedContext.autoCompleteSet, kb.getTopLevelUrlCompleteComponents() 54 | ); 55 | 56 | // override context to just check on id 57 | if (context.endpoint) { 58 | context.endpoint = context.endpoint.id; 59 | } 60 | 61 | delete context.otherTokenValues; 62 | 63 | function norm(t) { 64 | if (_.isString(t)) { 65 | return {name: t}; 66 | } 67 | return t; 68 | } 69 | 70 | if (context.autoCompleteSet) { 71 | context.autoCompleteSet = _.sortBy(_.map(context.autoCompleteSet, norm), 'name'); 72 | } 73 | if (expectedContext.autoCompleteSet) { 74 | expectedContext.autoCompleteSet = _.sortBy(_.map(expectedContext.autoCompleteSet, norm), 'name'); 75 | } 76 | 77 | deepEqual(context, expectedContext); 78 | } 79 | 80 | function t(term) { 81 | return {name: term, meta: "type"}; 82 | } 83 | 84 | function i(term) { 85 | return {name: term, meta: "index"}; 86 | } 87 | 88 | function index_test(name, tokenPath, otherTokenValues, expectedContext) { 89 | test(name, function () { 90 | var test_api = new kb._test.loadApisFromJson({ 91 | index_test: { 92 | endpoints: { 93 | _multi_indices: { 94 | patterns: ["{indices}/_multi_indices"] 95 | }, 96 | _single_index: {patterns: ["{index}/_single_index"]}, 97 | _no_index: { 98 | // testing default patters 99 | // patterns: ["_no_index"] 100 | } 101 | } 102 | } 103 | }, kb._test.globalUrlComponentFactories); 104 | 105 | kb.setActiveApi(test_api); 106 | 107 | mappings.loadMappings(MAPPING); 108 | testUrlContext(tokenPath, otherTokenValues, expectedContext); 109 | }); 110 | } 111 | 112 | index_test("Index integration 1", [], [], 113 | {autoCompleteSet: ["_no_index", i("index1"), i("index2")]} 114 | ); 115 | 116 | index_test("Index integration 2", [], ["index1"], 117 | // still return _no_index as index1 is not committed to yet. 118 | {autoCompleteSet: ["_no_index", i("index2")]} 119 | ); 120 | 121 | index_test("Index integration 2", ["index1"], [], 122 | {indices: ["index1"], autoCompleteSet: ["_multi_indices", "_single_index"]} 123 | ); 124 | 125 | index_test("Index integration 2", [ 126 | ["index1", "index2"] 127 | ], [], 128 | {indices: ["index1", "index2"], autoCompleteSet: ["_multi_indices"]} 129 | ); 130 | 131 | function type_test(name, tokenPath, otherTokenValues, expectedContext) { 132 | test(name, function () { 133 | var test_api = kb._test.loadApisFromJson({ 134 | "type_test": { 135 | endpoints: { 136 | _multi_types: {patterns: ["{indices}/{types}/_multi_types"]}, 137 | _single_type: {patterns: ["{indices}/{type}/_single_type"]}, 138 | _no_types: {patterns: ["{indices}/_no_types"]} 139 | } 140 | } 141 | }, kb._test.globalUrlComponentFactories); 142 | kb.setActiveApi(test_api); 143 | 144 | mappings.loadMappings(MAPPING); 145 | 146 | testUrlContext(tokenPath, otherTokenValues, expectedContext); 147 | 148 | }); 149 | } 150 | 151 | type_test("Type integration 1", ["index1"], [], 152 | {indices: ["index1"], autoCompleteSet: ["_no_types", t("type1.1"), t("type1.2")]} 153 | ); 154 | type_test("Type integration 2", ["index1"], ["type1.2"], 155 | // we are not yet comitted to type1.2, so _no_types is returned 156 | {indices: ["index1"], autoCompleteSet: ["_no_types", t("type1.1")]} 157 | ); 158 | 159 | type_test("Type integration 3", ["index2"], [], 160 | {indices: ["index2"], autoCompleteSet: ["_no_types", t("type2.1")]} 161 | ); 162 | 163 | type_test("Type integration 4", ["index1", "type1.2"], [], 164 | {indices: ["index1"], types: ["type1.2"], autoCompleteSet: ["_multi_types", "_single_type"]} 165 | ); 166 | 167 | type_test("Type integration 5", [ 168 | ["index1", "index2"], 169 | ["type1.2", "type1.1"] 170 | ], [], 171 | {indices: ["index1", "index2"], types: ["type1.2", "type1.1"], autoCompleteSet: ["_multi_types"]} 172 | ); 173 | -------------------------------------------------------------------------------- /public/tests/src/url_params_tests.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let url_params = require('../../src/autocomplete/url_params'); 3 | let autocomplete_engine = require('../../src/autocomplete/engine'); 4 | 5 | var {test, module, ok, fail, asyncTest, deepEqual, equal, start} = QUnit; 6 | 7 | module("Url params"); 8 | 9 | function param_test(name, description, tokenPath, expectedContext, globalParams) { 10 | 11 | test(name, function () { 12 | var urlParams = new url_params.UrlParams(description, globalParams || {}); 13 | if (typeof tokenPath === "string") { 14 | tokenPath = _.map(tokenPath.split("/"), function (p) { 15 | p = p.split(","); 16 | if (p.length === 1) { 17 | return p[0]; 18 | } 19 | return p; 20 | }); 21 | } 22 | 23 | if (expectedContext.autoCompleteSet) { 24 | expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { 25 | if (_.isString(t)) { 26 | t = {name: t} 27 | } 28 | return t; 29 | }); 30 | expectedContext.autoCompleteSet = _.sortBy(expectedContext.autoCompleteSet, 'name'); 31 | } 32 | 33 | var context = {}; 34 | 35 | autocomplete_engine.populateContext(tokenPath, context, null, 36 | expectedContext.autoCompleteSet, urlParams.getTopLevelComponents() 37 | ); 38 | 39 | 40 | if (context.autoCompleteSet) { 41 | context.autoCompleteSet = _.sortBy(context.autoCompleteSet, 'name'); 42 | } 43 | 44 | deepEqual(context, expectedContext); 45 | }); 46 | 47 | } 48 | 49 | function t(name, meta, insert_value) { 50 | var r = name; 51 | if (meta) { 52 | r = {name: name, meta: meta}; 53 | if (meta === "param" && !insert_value) { 54 | insert_value = name + "="; 55 | } 56 | } 57 | if (insert_value) { 58 | if (_.isString(r)) { 59 | r = {name: name} 60 | } 61 | r.insert_value = insert_value; 62 | } 63 | return r; 64 | } 65 | 66 | (function () { 67 | var params = { 68 | "a": ["1", "2"], 69 | "b": "__flag__" 70 | }; 71 | param_test("settings params", 72 | params, 73 | "a/1", 74 | {"a": ["1"]} 75 | ); 76 | 77 | param_test("autocomplete top level", 78 | params, 79 | [], 80 | {autoCompleteSet: [t("a", "param"), t("b", "flag")]} 81 | ); 82 | 83 | param_test("autocomplete top level, with defaults", 84 | params, 85 | [], 86 | {autoCompleteSet: [t("a", "param"), t("b", "flag"), t("c", "param")]}, 87 | { 88 | "c": [2] 89 | } 90 | ); 91 | 92 | param_test("autocomplete values", 93 | params, 94 | "a", 95 | {autoCompleteSet: [t("1", "a"), t("2", "a")]} 96 | ); 97 | 98 | param_test("autocomplete values flag", 99 | params, 100 | "b", 101 | {autoCompleteSet: [t("true", "b"), t("false", "b")]} 102 | ); 103 | 104 | 105 | })(); 106 | -------------------------------------------------------------------------------- /public/tests/tests.js: -------------------------------------------------------------------------------- 1 | require('ace'); 2 | 3 | require('ui/chrome') 4 | .setBrand({ 5 | logo: 'url(/plugins/sense/favicon.ico) center no-repeat', 6 | smallLogo: 'url(/plugins/sense/favicon.ico) center no-repeat' 7 | }) 8 | .setTabs([{ 9 | id: '', 10 | title: 'Sense Tests' 11 | }]) 12 | .setRootTemplate(require('./index.html')) 13 | .setRootController(function () { 14 | window.QUnit = require('qunit-1.10.0'); 15 | 16 | require('qunit-1.10.0.css'); 17 | require('ace'); 18 | /* global QUnit */ 19 | QUnit.config.autostart = false; 20 | QUnit.init(); 21 | 22 | require('./src/url_autocomplete_tests.js'); 23 | require('./src/url_params_tests.js'); 24 | require('./src/curl_parsing_tests.js'); 25 | require('./src/kb_tests.js'); 26 | require('./src/mapping_tests.js'); 27 | require('./src/editor_tests.js'); 28 | require('./src/tokenization_tests.js'); 29 | require('./src/integration_tests.js'); 30 | 31 | console.log('all tests loaded'); 32 | QUnit.start(); 33 | }); 34 | -------------------------------------------------------------------------------- /public/tests/webpackShims/qunit-1.10.0.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { 18 | font-size: small; 19 | } 20 | 21 | #qunit-tests { 22 | font-size: smaller; 23 | } 24 | 25 | /** Resets */ 26 | 27 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 28 | margin: 0; 29 | padding: 0; 30 | } 31 | 32 | /** Header */ 33 | 34 | #qunit-header { 35 | padding: 0.5em 0 0.5em 1em; 36 | 37 | color: #8699a4; 38 | background-color: #0d3349; 39 | 40 | font-size: 1.5em; 41 | line-height: 1em; 42 | font-weight: normal; 43 | 44 | border-radius: 5px 5px 0 0; 45 | -moz-border-radius: 5px 5px 0 0; 46 | -webkit-border-top-right-radius: 5px; 47 | -webkit-border-top-left-radius: 5px; 48 | } 49 | 50 | #qunit-header a { 51 | text-decoration: none; 52 | color: #c2ccd1; 53 | } 54 | 55 | #qunit-header a:hover, 56 | #qunit-header a:focus { 57 | color: #fff; 58 | } 59 | 60 | #qunit-testrunner-toolbar label { 61 | display: inline-block; 62 | padding: 0 .5em 0 .1em; 63 | } 64 | 65 | #qunit-banner { 66 | height: 5px; 67 | } 68 | 69 | #qunit-testrunner-toolbar { 70 | padding: 0.5em 0 0.5em 2em; 71 | color: #5E740B; 72 | background-color: #eee; 73 | overflow: hidden; 74 | } 75 | 76 | #qunit-userAgent { 77 | padding: 0.5em 0 0.5em 2.5em; 78 | background-color: #2b81af; 79 | color: #fff; 80 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 81 | } 82 | 83 | #qunit-modulefilter-container { 84 | float: right; 85 | } 86 | 87 | /** Tests: Pass/Fail */ 88 | 89 | #qunit-tests { 90 | list-style-position: inside; 91 | } 92 | 93 | #qunit-tests li { 94 | padding: 0.4em 0.5em 0.4em 2.5em; 95 | border-bottom: 1px solid #fff; 96 | list-style-position: inside; 97 | } 98 | 99 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 100 | display: none; 101 | } 102 | 103 | #qunit-tests li strong { 104 | cursor: pointer; 105 | } 106 | 107 | #qunit-tests li a { 108 | padding: 0.5em; 109 | color: #c2ccd1; 110 | text-decoration: none; 111 | } 112 | 113 | #qunit-tests li a:hover, 114 | #qunit-tests li a:focus { 115 | color: #000; 116 | } 117 | 118 | #qunit-tests ol { 119 | margin-top: 0.5em; 120 | padding: 0.5em; 121 | 122 | background-color: #fff; 123 | 124 | border-radius: 5px; 125 | -moz-border-radius: 5px; 126 | -webkit-border-radius: 5px; 127 | } 128 | 129 | #qunit-tests table { 130 | border-collapse: collapse; 131 | margin-top: .2em; 132 | } 133 | 134 | #qunit-tests th { 135 | text-align: right; 136 | vertical-align: top; 137 | padding: 0 .5em 0 0; 138 | } 139 | 140 | #qunit-tests td { 141 | vertical-align: top; 142 | } 143 | 144 | #qunit-tests pre { 145 | margin: 0; 146 | white-space: pre-wrap; 147 | word-wrap: break-word; 148 | } 149 | 150 | #qunit-tests del { 151 | background-color: #e0f2be; 152 | color: #374e0c; 153 | text-decoration: none; 154 | } 155 | 156 | #qunit-tests ins { 157 | background-color: #ffcaca; 158 | color: #500; 159 | text-decoration: none; 160 | } 161 | 162 | /*** Test Counts */ 163 | 164 | #qunit-tests b.counts { 165 | color: black; 166 | } 167 | 168 | #qunit-tests b.passed { 169 | color: #5E740B; 170 | } 171 | 172 | #qunit-tests b.failed { 173 | color: #710909; 174 | } 175 | 176 | #qunit-tests li li { 177 | padding: 5px; 178 | background-color: #fff; 179 | border-bottom: none; 180 | list-style-position: inside; 181 | } 182 | 183 | /*** Passing Styles */ 184 | 185 | #qunit-tests li li.pass { 186 | color: #3c510c; 187 | background-color: #fff; 188 | border-left: 10px solid #C6E746; 189 | } 190 | 191 | #qunit-tests .pass { 192 | color: #528CE0; 193 | background-color: #D2E0E6; 194 | } 195 | 196 | #qunit-tests .pass .test-name { 197 | color: #366097; 198 | } 199 | 200 | #qunit-tests .pass .test-actual, 201 | #qunit-tests .pass .test-expected { 202 | color: #999999; 203 | } 204 | 205 | #qunit-banner.qunit-pass { 206 | background-color: #C6E746; 207 | } 208 | 209 | /*** Failing Styles */ 210 | 211 | #qunit-tests li li.fail { 212 | color: #710909; 213 | background-color: #fff; 214 | border-left: 10px solid #EE5757; 215 | white-space: pre; 216 | } 217 | 218 | #qunit-tests > li:last-child { 219 | border-radius: 0 0 5px 5px; 220 | -moz-border-radius: 0 0 5px 5px; 221 | -webkit-border-bottom-right-radius: 5px; 222 | -webkit-border-bottom-left-radius: 5px; 223 | } 224 | 225 | #qunit-tests .fail { 226 | color: #000000; 227 | background-color: #EE5757; 228 | } 229 | 230 | #qunit-tests .fail .test-name, 231 | #qunit-tests .fail .module-name { 232 | color: #000000; 233 | } 234 | 235 | #qunit-tests .fail .test-actual { 236 | color: #EE5757; 237 | } 238 | 239 | #qunit-tests .fail .test-expected { 240 | color: green; 241 | } 242 | 243 | #qunit-banner.qunit-fail { 244 | background-color: #EE5757; 245 | } 246 | 247 | /** Result */ 248 | 249 | #qunit-testresult { 250 | padding: 0.5em 0.5em 0.5em 2.5em; 251 | 252 | color: #2b81af; 253 | background-color: #D2E0E6; 254 | 255 | border-bottom: 1px solid white; 256 | } 257 | 258 | #qunit-testresult .module-name { 259 | font-weight: bold; 260 | } 261 | 262 | /** Fixture */ 263 | 264 | #qunit-fixture { 265 | position: absolute; 266 | top: -10000px; 267 | left: -10000px; 268 | width: 1000px; 269 | height: 1000px; 270 | } 271 | -------------------------------------------------------------------------------- /public/webpackShims/ace.js: -------------------------------------------------------------------------------- 1 | require('ace/ace.js'); 2 | module.exports = window.ace; 3 | -------------------------------------------------------------------------------- /public/webpackShims/acequire.js: -------------------------------------------------------------------------------- 1 | module.exports = require('ace').require; 2 | -------------------------------------------------------------------------------- /public/webpackShims/zeroclip/zero_clipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/sense/5a901dc13d1dc19adf5a6dc0cf8cb9a6ac7f2059/public/webpackShims/zeroclip/zero_clipboard.swf -------------------------------------------------------------------------------- /public/webpackShims/zeroclip/zeroclip.js: -------------------------------------------------------------------------------- 1 | var ZeroClipboard = require('./zero_clipboard.js'); 2 | 3 | ZeroClipboard.config({ 4 | swfPath: require('file!./zero_clipboard.swf'), 5 | debug: false 6 | }); 7 | 8 | module.exports = ZeroClipboard; 9 | -------------------------------------------------------------------------------- /server/__tests__/proxy_config_collection.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import expect from 'expect.js'; 4 | import sinon from 'sinon'; 5 | import fs from 'fs'; 6 | import { Agent as HttpsAgent } from 'https'; 7 | 8 | import { ProxyConfigCollection } from '../proxy_config_collection' 9 | 10 | describe('ProxyConfigCollection', function () { 11 | beforeEach(function () { 12 | sinon.stub(fs, 'readFileSync', () => new Buffer(0)); 13 | }); 14 | 15 | afterEach(function () { 16 | fs.readFileSync.restore(); 17 | }); 18 | 19 | const proxyConfigs = [ 20 | { 21 | match: { 22 | protocol: 'https', 23 | host: 'localhost', 24 | port: 5601, 25 | path: '/.kibana' 26 | }, 27 | 28 | timeout: 1, 29 | }, 30 | 31 | { 32 | match: { 33 | protocol: 'https', 34 | host: 'localhost', 35 | port: 5601 36 | }, 37 | 38 | timeout: 2, 39 | }, 40 | 41 | { 42 | match: { 43 | host: 'localhost', 44 | port: 5601 45 | }, 46 | 47 | timeout: 3, 48 | }, 49 | 50 | { 51 | match: { 52 | host: 'localhost' 53 | }, 54 | 55 | timeout: 4, 56 | }, 57 | 58 | { 59 | match: {}, 60 | 61 | timeout: 5 62 | } 63 | ] 64 | 65 | function getTimeout(uri) { 66 | const collection = new ProxyConfigCollection(proxyConfigs); 67 | return collection.configForUri(uri).timeout; 68 | } 69 | 70 | context('http://localhost:5601', function () { 71 | it('defaults to the first matching timeout', function () { 72 | expect(getTimeout('http://localhost:5601')).to.be(3) 73 | }); 74 | }); 75 | 76 | context('https://localhost:5601/.kibana', function () { 77 | it('defaults to the first matching timeout', function () { 78 | expect(getTimeout('https://localhost:5601/.kibana')).to.be(1); 79 | }); 80 | }); 81 | 82 | context('http://localhost:5602', function () { 83 | it('defaults to the first matching timeout', function () { 84 | expect(getTimeout('http://localhost:5602')).to.be(4); 85 | }); 86 | }); 87 | 88 | context('https://localhost:5602', function () { 89 | it('defaults to the first matching timeout', function () { 90 | expect(getTimeout('https://localhost:5602')).to.be(4); 91 | }); 92 | }); 93 | 94 | context('http://localhost:5603', function () { 95 | it('defaults to the first matching timeout', function () { 96 | expect(getTimeout('http://localhost:5603')).to.be(4); 97 | }); 98 | }); 99 | 100 | context('https://localhost:5603', function () { 101 | it('defaults to the first matching timeout', function () { 102 | expect(getTimeout('https://localhost:5603')).to.be(4); 103 | }); 104 | }); 105 | 106 | context('https://localhost:5601/index', function () { 107 | it('defaults to the first matching timeout', function () { 108 | expect(getTimeout('https://localhost:5601/index')).to.be(2); 109 | }); 110 | }); 111 | 112 | context('http://localhost:5601/index', function () { 113 | it('defaults to the first matching timeout', function () { 114 | expect(getTimeout('http://localhost:5601/index')).to.be(3); 115 | }); 116 | }); 117 | 118 | context('https://localhost:5601/index/type', function () { 119 | it('defaults to the first matching timeout', function () { 120 | expect(getTimeout('https://localhost:5601/index/type')).to.be(2); 121 | }); 122 | }); 123 | 124 | context('http://notlocalhost', function () { 125 | it('defaults to the first matching timeout', function () { 126 | expect(getTimeout('http://notlocalhost')).to.be(5); 127 | }); 128 | }); 129 | 130 | context('collection with ssl config and root level verify:false', function () { 131 | function makeCollection() { 132 | return new ProxyConfigCollection([ 133 | { 134 | match: { host: '*.internal.org' }, 135 | ssl: { ca: ['path/to/ca'] } 136 | }, 137 | { 138 | match: { host: '*' }, 139 | ssl: { verify: false } 140 | } 141 | ]); 142 | } 143 | 144 | it('verifies for config that produces ssl agent', function () { 145 | const conf = makeCollection().configForUri('https://es.internal.org/_search'); 146 | expect(conf).to.have.property('rejectUnauthorized', true); 147 | expect(conf.agent).to.be.an(HttpsAgent); 148 | }); 149 | 150 | it('disabled verification for * config', function () { 151 | const conf = makeCollection().configForUri('https://extenal.org/_search'); 152 | expect(conf).to.have.property('rejectUnauthorized', false); 153 | expect(conf.agent).to.be(undefined); 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /server/__tests__/wildcard_matcher.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import expect from 'expect.js' 3 | 4 | import { WildcardMatcher } from '../wildcard_matcher' 5 | 6 | function should(candidate, ...constructorArgs) { 7 | if (!new WildcardMatcher(...constructorArgs).match(candidate)) { 8 | throw new Error(`Expected pattern ${[...constructorArgs]} to match ${candidate}`); 9 | } 10 | } 11 | 12 | function shouldNot(candidate, ...constructorArgs) { 13 | if (new WildcardMatcher(...constructorArgs).match(candidate)) { 14 | throw new Error(`Expected pattern ${[...constructorArgs]} to not match ${candidate}`); 15 | } 16 | } 17 | 18 | 19 | describe('WildcardMatcher', function () { 20 | context('pattern = *', function () { 21 | it('matches http', () => should('http', '*')); 22 | it('matches https', () => should('https', '*')); 23 | it('matches nothing', () => should('', '*')); 24 | it('does not match /', () => shouldNot('/', '*')); 25 | it('matches localhost', () => should('localhost', '*')); 26 | it('matches a path', () => should('/index/type/_search', '*')); 27 | 28 | context('defaultValue = /', function () { 29 | it('matches /', () => should('/', '*', '/')); 30 | }); 31 | }); 32 | 33 | context('pattern = http', function () { 34 | it('matches http', () => should('http', 'http')); 35 | it('does not match https', () => shouldNot('https', 'http')); 36 | it('does not match nothing', () => shouldNot('', 'http')); 37 | it('does not match localhost', () => shouldNot('localhost', 'http')); 38 | it('does not match a path', () => shouldNot('/index/type/_search', 'http')); 39 | }); 40 | 41 | context('pattern = 560{1..9}', function () { 42 | it('does not match http', () => shouldNot('http', '560{1..9}')); 43 | it('does not matches 5600', () => shouldNot('5600', '560{1..9}')); 44 | it('matches 5601', () => should('5601', '560{1..9}')); 45 | it('matches 5602', () => should('5602', '560{1..9}')); 46 | it('matches 5603', () => should('5603', '560{1..9}')); 47 | it('matches 5604', () => should('5604', '560{1..9}')); 48 | it('matches 5605', () => should('5605', '560{1..9}')); 49 | it('matches 5606', () => should('5606', '560{1..9}')); 50 | it('matches 5607', () => should('5607', '560{1..9}')); 51 | it('matches 5608', () => should('5608', '560{1..9}')); 52 | it('matches 5609', () => should('5609', '560{1..9}')); 53 | it('does not matches 5610', () => shouldNot('5610', '560{1..9}')); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /server/proxy_config.js: -------------------------------------------------------------------------------- 1 | import { memoize, values } from 'lodash' 2 | import { format as formatUrl } from 'url' 3 | import { Agent as HttpsAgent } from 'https' 4 | import { readFileSync } from 'fs' 5 | 6 | import { WildcardMatcher } from './wildcard_matcher' 7 | 8 | const makeHttpsAgent = memoize( 9 | opts => new HttpsAgent(opts), 10 | opts => JSON.stringify(opts) 11 | ) 12 | 13 | export class ProxyConfig { 14 | constructor(config) { 15 | config = Object.assign({}, config); 16 | 17 | // ----- 18 | // read "match" info 19 | // ----- 20 | const rawMatches = Object.assign({}, config.match); 21 | this.id = formatUrl({ 22 | protocol: rawMatches.protocol, 23 | hostname: rawMatches.host, 24 | port: rawMatches.port, 25 | pathname: rawMatches.path 26 | }) || '*'; 27 | 28 | this.matchers = { 29 | protocol: new WildcardMatcher(rawMatches.protocol), 30 | host: new WildcardMatcher(rawMatches.host), 31 | port: new WildcardMatcher(rawMatches.port), 32 | path: new WildcardMatcher(rawMatches.path, '/'), 33 | }; 34 | 35 | // ----- 36 | // read config vars 37 | // ----- 38 | this.timeout = config.timeout; 39 | this.sslAgent = this._makeSslAgent(config); 40 | } 41 | 42 | _makeSslAgent(config) { 43 | const ssl = config.ssl || {}; 44 | this.verifySsl = ssl.verify; 45 | 46 | const sslAgentOpts = { 47 | ca: ssl.ca && ssl.ca.map(ca => readFileSync(ca)), 48 | cert: ssl.cert && readFileSync(ssl.cert), 49 | key: ssl.key && readFileSync(ssl.key), 50 | }; 51 | 52 | if (values(sslAgentOpts).filter(Boolean).length) { 53 | return new HttpsAgent(sslAgentOpts); 54 | } 55 | } 56 | 57 | getForParsedUri({ protocol, hostname, port, pathname }) { 58 | let match = this.matchers.protocol.match(protocol.slice(0, -1)); 59 | match = match && this.matchers.host.match(hostname); 60 | match = match && this.matchers.port.match(port); 61 | match = match && this.matchers.path.match(pathname); 62 | 63 | if (!match) return {}; 64 | return { 65 | timeout: this.timeout, 66 | rejectUnauthorized: this.sslAgent ? true : this.verifySsl, 67 | agent: protocol === 'https:' ? this.sslAgent : undefined 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /server/proxy_config_collection.js: -------------------------------------------------------------------------------- 1 | import { defaultsDeep } from 'lodash' 2 | 3 | import { ProxyConfig } from './proxy_config' 4 | import { parse as parseUrl } from 'url' 5 | 6 | 7 | export class ProxyConfigCollection { 8 | constructor(configs = []) { 9 | this.configs = configs.map(settings => new ProxyConfig(settings)) 10 | } 11 | 12 | configForUri(uri) { 13 | const parsedUri = parseUrl(uri); 14 | const settings = this.configs.map(config => config.getForParsedUri(parsedUri)); 15 | return defaultsDeep({}, ...settings); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/wildcard_matcher.js: -------------------------------------------------------------------------------- 1 | import { Minimatch } from 'minimatch' 2 | 3 | export class WildcardMatcher { 4 | constructor(wildcardPattern, emptyVal) { 5 | this.emptyVal = emptyVal; 6 | this.pattern = String(wildcardPattern || '*'); 7 | this.matcher = new Minimatch(this.pattern, { 8 | noglobstar: true, 9 | dot: true, 10 | nocase: true, 11 | matchBase: true, 12 | nocomment: true 13 | }) 14 | } 15 | 16 | match(candidate) { 17 | const empty = !candidate || candidate === this.emptyVal; 18 | if (empty && this.pattern === '*') { 19 | return true; 20 | } 21 | 22 | return this.matcher.match(candidate || '') 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tasks/build.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('build', [ 4 | 'clean:build', 5 | 'clean:target', 6 | 'eslint:source', 7 | 'copy:build', 8 | 'run:npmInstallInBuild', 9 | 'gitinfo', 10 | 'replace:build', 11 | 'compress:build' 12 | ]); 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /tasks/release.js: -------------------------------------------------------------------------------- 1 | var readline = require('readline'); 2 | 3 | module.exports = function (grunt) { 4 | 5 | grunt.registerTask('_release:confirmUpload', function () { 6 | var rl = readline.createInterface({ 7 | input: process.stdin, 8 | output: process.stdout 9 | }); 10 | 11 | rl.on('close', this.async()); 12 | rl.question('Do you want to actually upload the files to s3 after building?, [N/y] ', function (resp) { 13 | var debug = resp.toLowerCase().trim()[0] !== 'y'; 14 | grunt.config.set('s3.release.options.dryRun', debug); 15 | rl.close(); 16 | }); 17 | }); 18 | 19 | // collect the key and secret from the .aws-config.json file, finish configuring the s3 task 20 | grunt.registerTask('_release:loadS3Config', function () { 21 | var config = grunt.file.readJSON('.aws-config.json'); 22 | grunt.config('s3.release.options.accessKeyId', config.key); 23 | grunt.config('s3.release.options.secretAccessKey', config.secret); 24 | }); 25 | 26 | grunt.registerTask('release', [ 27 | '_release:confirmUpload', 28 | '_release:loadS3Config', 29 | 'build', 30 | 's3:release' 31 | ]); 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /tasks/setup_kibana.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').execFileSync; 2 | const stat = require('fs').statSync; 3 | 4 | const fromRoot = require('path').resolve.bind(null, __dirname, '../'); 5 | 6 | module.exports = function (grunt) { 7 | grunt.registerTask('setup_kibana', function () { 8 | const kbnDir = fromRoot('../kibana'); 9 | const kbnGitDir = fromRoot('../kibana/.git'); 10 | 11 | try { 12 | if (stat(kbnGitDir).isDirectory()) { 13 | exec('git', ['pull', 'origin', 'master'], { cwd: kbnDir }); 14 | } else { 15 | throw new Error(`${kbnGitDir} is not a directory??`); 16 | } 17 | } catch (error) { 18 | if (error.code === 'ENOENT') { 19 | exec('git', ['clone', 'https://github.com/elastic/kibana.git', kbnDir]); 20 | } else { 21 | throw error; 22 | } 23 | } 24 | 25 | exec('npm', ['prune'], { cwd: kbnDir }); 26 | exec('npm', ['install'], { cwd: kbnDir }); 27 | }); 28 | }; 29 | --------------------------------------------------------------------------------