"
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 |
31 |
32 |
GET _search
33 | {
34 | "query": { "match_all": {} }
35 | }
36 |
37 |
38 |
︙
39 |
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 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
59 |
60 |
61 |
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 |
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 | Get to work
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 |
34 |
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 |
--------------------------------------------------------------------------------