DevTools - partially de-angularize and move custom views out of top_nav (#39341)

* Sense settings modal

* Deleted comments

* DevTools help flyout

* Settings UI improvements

* Improve file naming

* Rename helpShowPanel

* Added TS to help

* React welcome flyout + removed it from top nav

* Move history tool outside of top nav
Improve it's behavior - now it will update when open, when a query is run.

* ts fix

* Code review fixes

* Code review fixes

* Fixed translations

* Code review changes - autoclose panels

* deleted unused useEffect

* fix z-order of components

* type check fixes

* Final code review fixes
This commit is contained in:
Liza Katz 2019-06-26 19:39:55 +03:00 committed by GitHub
parent 4f28b25daf
commit 2ac205824a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 827 additions and 584 deletions

View file

@ -27,9 +27,6 @@ require('ui/capabilities/route_setup');
require('./src/controllers/sense_controller');
require('./src/directives/sense_history');
require('./src/directives/sense_settings');
require('./src/directives/sense_help');
require('./src/directives/sense_welcome');
require('./src/directives/console_menu_directive');

View file

@ -1,4 +1,6 @@
<kbn-top-nav name="console" config="topNavMenu"></kbn-top-nav>
<kbn-dev-tools-app data-test-subj="console" top-nav-config="topNavController">
<sense-history ng-show="showHistory" is-shown="showHistory" history-dirty="lastRequestTimestamp"></sense-history>
<div class="conApp">
<div class="conApp__editor">
<ul class="conApp__autoComplete" id="autocomplete"></ul>
@ -32,3 +34,6 @@
</div>
</div>
</kbn-dev-tools-app>
<div id="consoleWelcomePanel"></div>
<div id="consoleHelpPanel"></div>
<div id="consoleSettingsModal"></div>

View file

@ -17,14 +17,14 @@
* under the License.
*/
require('./sense_help_example');
import template from './help.html';
require('ui/modules')
.get('app/sense')
.directive('senseHelp', function () {
return {
restrict: 'E',
template
};
});
export interface DevToolsSettings {
fontSize: number;
wrapMode: boolean;
autocomplete: {
fields: boolean;
indices: boolean;
templates: boolean;
};
polling: boolean;
tripleQuotes: boolean;
}

View file

@ -17,25 +17,31 @@
* under the License.
*/
const SenseEditor = require('../sense_editor/editor');
import React, { useEffect } from 'react';
// @ts-ignore
import exampleText from 'raw-loader!./helpExample.txt';
import { applyResizeCheckerToEditors } from '../sense_editor_resize';
import $ from 'jquery';
// @ts-ignore
import SenseEditor from '../sense_editor/editor';
require('ui/modules')
.get('app/sense')
.directive('senseHelpExample', function () {
return {
restrict: 'E',
link: function ($scope, $el) {
$el.text(exampleText.trim());
$scope.editor = new SenseEditor($el);
applyResizeCheckerToEditors($scope, $el, $scope.editor);
$scope.editor.setReadOnly(true);
$scope.editor.$blockScrolling = Infinity;
interface EditorExampleProps {
panel: string;
}
$scope.$on('$destroy', function () {
if ($scope.editor) $scope.editor.destroy();
});
}
export function EditorExample(props: EditorExampleProps) {
const elemId = `help-example-${props.panel}`;
useEffect(() => {
const el = $(`#${elemId}`);
el.text(exampleText.trim());
const editor = new SenseEditor(el);
editor.setReadOnly(true);
editor.$blockScrolling = Infinity;
return () => {
editor.destroy();
};
});
}, []);
return <div id={elemId} className="conHelp__example" />;
}

View file

@ -0,0 +1,144 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiText,
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiTitle,
EuiSpacer,
} from '@elastic/eui';
import { EditorExample } from './editor_example';
interface Props {
onClose: () => void;
}
export function HelpPanel(props: Props) {
return (
<EuiFlyout onClose={props.onClose} data-test-subj="helpFlyout" size="s">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>
<FormattedMessage id="console.helpPage.pageTitle" defaultMessage="Help" />
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>
<h3>
<FormattedMessage
defaultMessage="Request format"
id="console.helpPage.requestFormatTitle"
/>
</h3>
<p>
<FormattedMessage
id="console.helpPage.requestFormatDescription"
defaultMessage="You can type one or more requests in the white editor. Console understands requests in a compact format:"
/>
</p>
<EditorExample panel="help" />
<h3>
<FormattedMessage
id="console.helpPage.keyboardCommandsTitle"
defaultMessage="Keyboard commands"
/>
</h3>
<EuiSpacer />
<dl>
<dt>Ctrl/Cmd + I</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.autoIndentDescription"
defaultMessage="Auto indent current request"
/>
</dd>
<dt>Ctrl/Cmd + /</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.openDocumentationDescription"
defaultMessage="Open documentation for current request"
/>
</dd>
<dt>Ctrl + Space</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.openAutoCompleteDescription"
defaultMessage="Open Auto complete (even if not typing)"
/>
</dd>
<dt>Ctrl/Cmd + Enter</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.submitRequestDescription"
defaultMessage="Submit request"
/>
</dd>
<dt>Ctrl/Cmd + Up/Down</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.jumpToPreviousNextRequestDescription"
defaultMessage="Jump to the previous/next request start or end."
/>
</dd>
<dt>Ctrl/Cmd + Alt + L</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.collapseExpandCurrentScopeDescription"
defaultMessage="Collapse/expand current scope."
/>
</dd>
<dt>Ctrl/Cmd + Option + 0</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.collapseAllScopesDescription"
defaultMessage="Collapse all scopes but the current one. Expand by adding a shift."
/>
</dd>
<dt>Down arrow</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.switchFocusToAutoCompleteMenuDescription"
defaultMessage="Switch focus to auto-complete menu. Use arrows to further select a term"
/>
</dd>
<dt>Enter/Tab</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.selectCurrentlySelectedInAutoCompleteMenuDescription"
defaultMessage="Select the currently selected or the top most term in auto-complete menu"
/>
</dd>
<dt>Esc</dt>
<dd>
<FormattedMessage
id="console.helpPage.keyboardCommands.closeAutoCompleteMenuDescription"
defaultMessage="Close auto-complete menu"
/>
</dd>
</dl>
</EuiText>
</EuiFlyoutBody>
</EuiFlyout>
);
}

View file

@ -0,0 +1,249 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
EuiButtonEmpty,
EuiFieldNumber,
EuiFormRow,
EuiCheckboxGroup,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiOverlayMask,
EuiSwitch,
} from '@elastic/eui';
import { DevToolsSettings } from './dev_tools_settings';
export type AutocompleteOptions = 'fields' | 'indices' | 'templates';
interface Props {
onSaveSettings: (newSettings: DevToolsSettings) => Promise<void>;
onClose: () => void;
refreshAutocompleteSettings: () => void;
settings: DevToolsSettings;
}
export function DevToolsSettingsModal(props: Props) {
const [fontSize, setFontSize] = useState(props.settings.fontSize);
const [wrapMode, setWrapMode] = useState(props.settings.wrapMode);
const [fields, setFields] = useState(props.settings.autocomplete.fields);
const [indices, setIndices] = useState(props.settings.autocomplete.indices);
const [templates, setTemplates] = useState(props.settings.autocomplete.templates);
const [polling, setPolling] = useState(props.settings.polling);
const [tripleQuotes, setTripleQuotes] = useState(props.settings.tripleQuotes);
const autoCompleteCheckboxes = [
{
id: 'fields',
label: i18n.translate('console.settingsPage.fieldsLabelText', {
defaultMessage: 'Fields',
}),
stateSetter: setFields,
},
{
id: 'indices',
label: i18n.translate('console.settingsPage.indicesAndAliasesLabelText', {
defaultMessage: 'Indices & Aliases',
}),
stateSetter: setIndices,
},
{
id: 'templates',
label: i18n.translate('console.settingsPage.templatesLabelText', {
defaultMessage: 'Templates',
}),
stateSetter: setTemplates,
},
];
const checkboxIdToSelectedMap = {
fields,
indices,
templates,
};
const onAutocompleteChange = (optionId: AutocompleteOptions) => {
const option = _.find(autoCompleteCheckboxes, item => item.id === optionId);
if (option) {
option.stateSetter(!checkboxIdToSelectedMap[optionId]);
}
};
function saveSettings() {
props.onSaveSettings({
fontSize,
wrapMode,
autocomplete: {
fields,
indices,
templates,
},
polling,
tripleQuotes,
});
}
return (
<EuiOverlayMask>
<EuiModal data-test-subj="devToolsSettingsModal" onClose={props.onClose}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
id="console.settingsPage.pageTitle"
defaultMessage="Console Settings"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiFormRow
label={
<FormattedMessage
id="console.settingsPage.fontSizeLabel"
defaultMessage="Font Size"
/>
}
>
<EuiFieldNumber
autoFocus
data-test-subj="setting-font-size-input"
value={fontSize}
min={6}
max={50}
onChange={e => {
const val = parseInt(e.target.value, 10);
if (!val) return;
setFontSize(val);
}}
/>
</EuiFormRow>
<EuiFormRow>
<EuiSwitch
checked={wrapMode}
data-test-subj="settingsWrapLines"
id="wrapLines"
label={
<FormattedMessage
defaultMessage="Wrap long lines"
id="console.settingsPage.wrapLongLinesLabelText"
/>
}
onChange={e => setWrapMode(e.target.checked)}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="console.settingsPage.jsonSyntaxLabel"
defaultMessage="JSON syntax"
/>
}
>
<EuiSwitch
checked={tripleQuotes}
data-test-subj="tripleQuotes"
id="tripleQuotes"
label={
<FormattedMessage
defaultMessage="Use triple quotes in output pane"
id="console.settingsPage.tripleQuotesMessage"
/>
}
onChange={e => setTripleQuotes(e.target.checked)}
/>
</EuiFormRow>
<EuiFormRow
labelType="legend"
label={
<FormattedMessage
id="console.settingsPage.autocompleteLabel"
defaultMessage="Autocomplete"
/>
}
>
<EuiCheckboxGroup
options={autoCompleteCheckboxes}
idToSelectedMap={checkboxIdToSelectedMap}
onChange={(e: any) => {
onAutocompleteChange(e as AutocompleteOptions);
}}
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="console.settingsPage.refreshingDataLabel"
defaultMessage="Refreshing autocomplete suggestions"
/>
}
helpText={
<FormattedMessage
id="console.settingsPage.refreshingDataDescription"
defaultMessage="Console refreshes autocomplete suggestions by querying Elasticsearch.
Automatic refreshes may be an issue if you have a large cluster or if you have network limitations."
/>
}
>
<EuiSwitch
checked={polling}
data-test-subj="autocompletePolling"
id="autocompletePolling"
label={
<FormattedMessage
defaultMessage="Automatically refresh autocomplete suggestions"
id="console.settingsPage.pollingLabelText"
/>
}
onChange={e => setPolling(e.target.checked)}
/>
</EuiFormRow>
<EuiButton
data-test-subj="autocompletePolling"
id="autocompletePolling"
onClick={props.refreshAutocompleteSettings}
>
<FormattedMessage
defaultMessage="Refresh autocomplete suggestions"
id="console.settingsPage.refreshButtonLabel"
/>
</EuiButton>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty data-test-subj="settingsCancelButton" onClick={props.onClose}>
<FormattedMessage id="console.settingsPage.cancelButtonLabel" defaultMessage="Cancel" />
</EuiButtonEmpty>
<EuiButton fill data-test-subj="settings-save-button" onClick={saveSettings}>
<FormattedMessage id="console.settingsPage.saveButtonLabel" defaultMessage="Save" />
</EuiButton>
</EuiModalFooter>
</EuiModal>
</EuiOverlayMask>
);
}

View file

@ -0,0 +1,134 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
// @ts-ignore
import {
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiTitle,
EuiButton,
EuiText,
EuiFlyoutFooter,
} from '@elastic/eui';
import { EditorExample } from './editor_example';
interface Props {
onDismiss: () => void;
}
export function WelcomePanel(props: Props) {
return (
<EuiFlyout onClose={props.onDismiss} data-test-subj="welcomePanel" size="s">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>
<FormattedMessage
id="console.welcomePage.pageTitle"
defaultMessage="Welcome to Console"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>
<h4>
<FormattedMessage
id="console.welcomePage.quickIntroTitle"
defaultMessage="Quick intro to the UI"
/>
</h4>
<p>
<FormattedMessage
id="console.welcomePage.quickIntroDescription"
defaultMessage="The Console UI is split into two panes: an editor pane (left) and a response pane (right).
Use the editor to type requests and submit them to Elasticsearch. The results will be displayed in
the response pane on the right side."
/>
</p>
<p>
<FormattedMessage
id="console.welcomePage.supportedRequestFormatTitle"
defaultMessage="Console understands requests in a compact format, similar to cURL:"
/>
</p>
<EditorExample panel="welcome" />
<p>
<FormattedMessage
id="console.welcomePage.supportedRequestFormatDescription"
defaultMessage="While typing a request, Console will make suggestions which you can then accept by hitting Enter/Tab.
These suggestions are made based on the request structure as well as your indices and types."
/>
</p>
<h4>
<FormattedMessage
id="console.welcomePage.quickTipsTitle"
defaultMessage="A few quick tips, while I have your attention"
/>
</h4>
<ul>
<li>
<FormattedMessage
id="console.welcomePage.quickTips.submitRequestDescription"
defaultMessage="Submit requests to ES using the green triangle button."
/>
</li>
<li>
<FormattedMessage
id="console.welcomePage.quickTips.useWrenchMenuDescription"
defaultMessage="Use the wrench menu for other useful things."
/>
</li>
<li>
<FormattedMessage
id="console.welcomePage.quickTips.cUrlFormatForRequestsDescription"
defaultMessage="You can paste requests in cURL format and they will be translated to the Console syntax."
/>
</li>
<li>
<FormattedMessage
id="console.welcomePage.quickTips.resizeEditorDescription"
defaultMessage="You can resize the editor and output panes by dragging the separator between them."
/>
</li>
<li>
<FormattedMessage
id="console.welcomePage.quickTips.keyboardShortcutsDescription"
defaultMessage="Study the keyboard shortcuts under the Help button. Good stuff in there!"
/>
</li>
</ul>
</EuiText>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiButton
fill={true}
fullWidth={false}
data-test-subj="help-close-button"
onClick={props.onDismiss}
>
<FormattedMessage id="console.welcomePage.closeButtonLabel" defaultMessage="Dismiss" />
</EuiButton>
</EuiFlyoutFooter>
</EuiFlyout>
);
}

View file

@ -23,10 +23,15 @@ import $ from 'jquery';
import { initializeInput } from '../input';
import { initializeOutput } from '../output';
import init from '../app';
import { SenseTopNavController } from './sense_top_nav_controller';
import { getEndpointFromPosition } from '../autocomplete';
import { DOC_LINK_VERSION } from 'ui/documentation_links';
// welcome message
import { showWelcomePanel } from '../helpers/welcome_show_panel';
import storage from '../storage';
import { getTopNavConfig } from '../helpers/get_top_nav';
const module = require('ui/modules').get('app/sense');
module.run(function ($rootScope) {
@ -35,10 +40,19 @@ module.run(function ($rootScope) {
};
});
module.controller('SenseController', function SenseController(Private, $scope, $timeout, $location, kbnUiAceKeyboardModeService) {
function showWelcomeMessageIfNeeded($scope) {
if (storage.get('version_welcome_shown') !== '@@SENSE_REVISION') {
const hideWelcomePanel = showWelcomePanel();
$scope.$on('$destroy', () => {
hideWelcomePanel();
});
}
}
module.controller('SenseController', function SenseController($scope, $timeout, $location, kbnUiAceKeyboardModeService) {
docTitle.change('Console');
$scope.topNavController = Private(SenseTopNavController);
showWelcomeMessageIfNeeded($scope);
// Since we pass this callback via reactDirective into a react component, which has the function defined as required
// in it's prop types, we should set this initially (before it's set in the $timeout below). Without this line
@ -85,6 +99,15 @@ module.controller('SenseController', function SenseController(Private, $scope, $
}
});
};
$scope.showHistory = false;
$scope.historyDirty = undefined;
$scope.toggleHistory = () => {
$scope.showHistory = !$scope.showHistory;
};
$scope.topNavMenu = getTopNavConfig($scope, $scope.toggleHistory);
$scope.openDocumentation = () => {
if (!$scope.documentation) {
return;
@ -94,7 +117,11 @@ module.controller('SenseController', function SenseController(Private, $scope, $
$scope.sendSelected = () => {
input.focus();
input.sendCurrentRequestToES();
input.sendCurrentRequestToES(() => {
// History watches this value and will re-render itself when it changes, so that
// the list of requests stays up-to-date as new requests are sent.
$scope.lastRequestTimestamp = new Date().getTime();
});
return false;
};

View file

@ -1,3 +1,7 @@
sense-history {
padding: $euiSizeS;
}
.conHistory__body {
display: flex;
height: $euiSizeXL * 10;

View file

@ -23,7 +23,7 @@ import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/sense', ['react']);
import { ConsoleMenu } from '../console_menu';
import { ConsoleMenu } from '../components/console_menu';
module.directive('consoleMenu', function (reactDirective) {
return reactDirective(wrapInI18nContext(ConsoleMenu));

View file

@ -1,95 +0,0 @@
<h2
class="euiTitle euiTitle--small"
i18n-id="console.helpPage.pageTitle"
i18n-default-message="Help"
></h2>
<div class="euiSpacer euiSpacer--m"></div>
<div class="euiPanel euiPanel--paddingLarge">
<div class="euiFlexGroup euiFlexGroup--gutterExtraLarge">
<div class="euiFlexItem">
<h3
class="euiTitle euiTitle--xsmall"
i18n-id="console.helpPage.requestFormatTitle"
i18n-default-message="Request format"
></h3>
<div class="euiSpacer euiSpacer--l"></div>
<span
i18n-id="console.helpPage.requestFormatDescription"
i18n-default-message="You can type one or more requests in the white editor. Console understands requests in a compact format:"
></span>
<sense-help-example class="conHelp__example"></sense-help-example>
</div>
<div class="euiFlexItem">
<h3
i18n-id="console.helpPage.keyboardCommandsTitle"
i18n-default-message="Keyboard commands"
class="euiTitle euiTitle--xsmall"
></h3>
<div class="euiSpacer euiSpacer--l"></div>
<dl class="euiDescriptionList euiDescriptionList--row euiDescriptionList--compressed">
<dt class="euiDescriptionList__title">Ctrl/Cmd + I</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.autoIndentDescription"
i18n-default-message="Auto indent current request"
></dd>
<dt class="euiDescriptionList__title">Ctrl/Cmd + /</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.openDocumentationDescription"
i18n-default-message="Open documentation for current request"
></dd>
<dt class="euiDescriptionList__title">Ctrl + Space</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.openAutoCompleteDescription"
i18n-default-message="Open Auto complete (even if not typing)"
></dd>
<dt class="euiDescriptionList__title">Ctrl/Cmd + Enter</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.submitRequestDescription"
i18n-default-message="Submit request"
></dd>
<dt class="euiDescriptionList__title">Ctrl/Cmd + Up/Down</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.jumpToPreviousNextRequestDescription"
i18n-default-message="Jump to the previous/next request start or end."
></dd>
<dt class="euiDescriptionList__title">Ctrl/Cmd + Alt + L</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.collapseExpandCurrentScopeDescription"
i18n-default-message="Collapse/expand current scope."
></dd>
<dt class="euiDescriptionList__title">Ctrl/Cmd + Option + 0</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.collapseAllScopesDescription"
i18n-default-message="Collapse all scopes but the current one. Expand by adding a shift."
></dd>
<dt class="euiDescriptionList__title">Down arrow</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.switchFocusToAutoCompleteMenuDescription"
i18n-default-message="Switch focus to auto-complete menu. Use arrows to further select a term"
></dd>
<dt class="euiDescriptionList__title">Enter/Tab</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.selectCurrentlySelectedInAutoCompleteMenuDescription"
i18n-default-message="Select the currently selected or the top most term in auto-complete menu"
></dd>
<dt class="euiDescriptionList__title">Esc</dt>
<dd
class="euiDescriptionList__description"
i18n-id="console.helpPage.keyboardCommands.closeAutoCompleteMenuDescription"
i18n-default-message="Close auto-complete menu"
></dd>
</dl>
</div>
</div>
</div>

View file

@ -33,35 +33,48 @@ require('ui/modules')
restrict: 'E',
template,
controllerAs: 'history',
scope: {
isShown: '=',
historyDirty: '=',
},
controller: function ($scope, $element) {
this.reqs = history.getHistory();
this.selectedIndex = 0;
this.selectedReq = this.reqs[this.selectedIndex];
this.viewingReq = this.selectedReq;
// calculate the text description of a request
this.describeReq = memoize((req) => {
const endpoint = req.endpoint;
const date = moment(req.time);
let formattedDate = date.format('MMM D');
if (date.diff(moment(), 'days') > -7) {
formattedDate = date.fromNow();
}
return `${endpoint} (${formattedDate})`;
$scope.$watch('historyDirty', () => {
this.init();
});
this.describeReq.cache = new WeakMap();
$scope.$watch('isShown', () => {
if ($scope.isShown) this.init();
});
this.init = () => {
this.reqs = history.getHistory();
this.selectedIndex = 0;
this.selectedReq = this.reqs[this.selectedIndex];
this.viewingReq = this.selectedReq;
// calculate the text description of a request
this.describeReq = memoize((req) => {
const endpoint = req.endpoint;
const date = moment(req.time);
let formattedDate = date.format('MMM D');
if (date.diff(moment(), 'days') > -7) {
formattedDate = date.fromNow();
}
return `${endpoint} (${formattedDate})`;
});
this.describeReq.cache = new WeakMap();
};
// main actions
this.clear = () => {
history.clearHistory($element);
$scope.kbnTopNav.close();
this.init();
};
this.restore = (req = this.selectedReq) => {
history.restoreFromHistory(req);
$scope.kbnTopNav.close();
};
this.onKeyDown = (ev) => {

View file

@ -1,89 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
require('ui/directives/input_focus');
import template from './settings.html';
import { getAutocomplete, getCurrentSettings, updateSettings, getPolling } from '../settings';
import mappings from '../mappings';
require('ui/modules')
.get('app/sense')
.directive('senseSettings', function () {
return {
restrict: 'E',
template,
controllerAs: 'settings',
controller: function ($scope, $element) {
this.vals = getCurrentSettings();
this.isPollingVisible = () => {
const selectedAutoCompleteOptions =
Object.keys(this.vals.autocomplete).filter(key => this.vals.autocomplete[key]);
return selectedAutoCompleteOptions.length > 0;
};
this.refresh = () => {
mappings.retrieveAutoCompleteInfo();
};
this.saveSettings = () => {
const prevSettings = getAutocomplete();
const prevPolling = getPolling();
this.vals = updateSettings(this.vals);
// We'll only retrieve settings if polling is on.
if (getPolling()) {
// Find which, if any, autocomplete settings have changed.
const settingsDiff = Object.keys(prevSettings).filter(key => prevSettings[key] !== this.vals.autocomplete[key]);
const changedSettings = settingsDiff.reduce((changedSettingsAccum, setting) => {
changedSettingsAccum[setting] = this.vals.autocomplete[setting];
return changedSettingsAccum;
}, {});
const isSettingsChanged = settingsDiff.length > 0;
const isPollingChanged = prevPolling !== getPolling();
if (isSettingsChanged) {
// If the user has changed one of the autocomplete settings, then we'll fetch just the
// ones which have changed.
mappings.retrieveAutoCompleteInfo(changedSettings);
} else if (isPollingChanged) {
// If the user has turned polling on, then we'll fetch all selected autocomplete settings.
mappings.retrieveAutoCompleteInfo();
}
}
$scope.kbnTopNav.close();
};
const self = this;
function onEnter(event) {
if (event.which === 13) {
self.saveSettings();
}
}
const boundElement = $element.bind('keydown', onEnter);
$scope.$on('$destroy', () => boundElement.unbind('keydown', onEnter));
},
};
});

View file

@ -1,213 +0,0 @@
<h2
class="kuiLocalDropdownTitle"
i18n-id="console.settingsPage.pageTitle"
i18n-default-message="Settings"
></h2>
<form class="form" name="settingsForm" ng-submit="settingsForm.$valid && settings.saveSettings()">
<fieldset
class="kuiVerticalRhythm"
role="group"
>
<div class="kuiLocalDropdownHeader">
<legend
id="consoleFontSize"
class="kuiLocalDropdownHeader__label"
i18n-id="console.settingsPage.fontSizeLabel"
i18n-default-message="Font size"
></legend>
</div>
<input
input-focus
ng-model="settings.vals.fontSize"
name="fontSize"
type="number"
required
class="kuiLocalDropdownInput kuiVerticalRhythmSmall"
aria-labelledby="consoleFontSize"
data-test-subj="setting-font-size-input"
>
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
type="checkbox"
ng-model="settings.vals.wrapMode"
aria-describedby="consoleFontSize"
>
<span
class="kuiCheckBoxLabel__text"
i18n-id="console.settingsPage.wrapLongLinesLabelText"
i18n-default-message="Wrap long lines"
></span>
</label>
</fieldset>
<fieldset
class="kuiVerticalRhythm"
role="group"
>
<div class="kuiLocalDropdownHeader">
<legend
class="kuiLocalDropdownHeader__label"
i18n-id="console.settingsPage.jsonLabel"
i18n-default-message="JSON syntax"
></legend>
</div>
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
name="jsonTripleQuotes"
type="checkbox"
ng-model="settings.vals.tripleQuotes"
>
<span
class="kuiCheckBoxLabel__text"
i18n-id="console.settingsPage.tripleQuotesLabelText"
i18n-default-message="Use triple quotes in output pane"
></span>
</label>
</fieldset>
<fieldset
class="kuiVerticalRhythm"
role="group"
>
<div class="kuiLocalDropdownHeader">
<legend
id="consoleAutocomplete"
class="kuiLocalDropdownHeader__label"
i18n-id="console.settingsPage.autocompleteLabel"
i18n-default-message="Autocomplete"
></legend>
</div>
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
name="autocompleteFields"
type="checkbox"
ng-model="settings.vals.autocomplete.fields"
aria-describedby="consoleAutocomplete"
>
<span
class="kuiCheckBoxLabel__text"
i18n-id="console.settingsPage.fieldsLabelText"
i18n-default-message="Fields"
></span>
</label>
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
name="autocompleteIndices"
type="checkbox"
ng-model="settings.vals.autocomplete.indices"
aria-describedby="consoleAutocomplete"
>
<span
class="kuiCheckBoxLabel__text"
i18n-id="console.settingsPage.indicesAndAliasesLabelText"
i18n-default-message="Indices &amp; Aliases"
></span>
</label>
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
name="autocompleteTemplates"
type="checkbox"
ng-model="settings.vals.autocomplete.templates"
aria-describedby="consoleAutocomplete"
>
<span
class="kuiCheckBoxLabel__text"
i18n-id="console.settingsPage.templatesLabelText"
i18n-default-message="Templates"
></span>
</label>
</fieldset>
<fieldset
class="kuiVerticalRhythm"
role="group"
ng-show="settings.isPollingVisible()"
>
<div class="kuiLocalDropdownHeader">
<legend
class="kuiLocalDropdownHeader__label"
i18n-id="console.settingsPage.refreshingDataLabel"
i18n-default-message="Refreshing autocomplete suggestions"
></legend>
</div>
<p
class="kuiVerticalRhythmSmall"
id="consoleRefreshingData"
i18n-id="console.settingsPage.refreshingDataDescription"
i18n-default-message="Console refreshes autocomplete suggestions by querying Elasticsearch.
Automatic refreshes may be an issue if you have a large cluster or if you have network limitations."
></p>
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
name="polling"
type="checkbox"
ng-model="settings.vals.polling"
aria-describedby="consoleRefreshingData"
>
<span
class="kuiCheckBoxLabel__text"
i18n-id="console.settingsPage.pollingLabelText"
i18n-default-message="Automatically refresh autocomplete suggestions"
></span>
</label>
<button
type="button"
ng-click="settings.refresh()"
class="euiButton euiButton--primary kuiVerticalRhythmSmall"
>
<span class="euiButton__content">
<span
class="euiButton__text"
i18n-id="console.settingsPage.refreshButtonLabel"
i18n-default-message="Refresh autocomplete suggestions"
></span>
</span>
</button>
</fieldset>
<div class="kuiVerticalRhythm">
<button
ng-click="kbnTopNav.close()"
class="euiButton euiButton--primary"
>
<span class="euiButton__content">
<span
class="euiButton__text"
i18n-id="console.settingsPage.cancelButtonLabel"
i18n-default-message="Cancel"
></span>
</span>
</button>
<button
type="submit"
ng-disabled="settingsForm.$invalid"
class="euiButton euiButton--primary euiButton--fill"
data-test-subj="settings-save-button"
>
<span class="euiButton__content">
<span
class="euiButton__text"
i18n-id="console.settingsPage.saveButtonLabel"
i18n-default-message="Save"
></span>
</span>
</button>
</div>
</form>

View file

@ -1,76 +0,0 @@
<div class="euiText">
<h2
i18n-id="console.welcomePage.pageTitle"
i18n-default-message="Welcome to Console"
></h2>
<p>
<strong
i18n-id="console.welcomePage.quickIntroTitle"
i18n-default-message="Quick intro to the UI"
></strong>
</p>
<p
i18n-id="console.welcomePage.quickIntroDescription"
i18n-default-message="The Console UI is split into two panes: an editor pane (left) and a response pane (right).
Use the editor to type requests and submit them to Elasticsearch. The results will be displayed in
the response pane on the right side."
></p>
<p
i18n-id="console.welcomePage.supportedRequestFormatTitle"
i18n-default-message="Console understands requests in a compact format, similar to cURL:"
></p>
<sense-help-example></sense-help-example>
<p
i18n-id="console.welcomePage.supportedRequestFormatDescription"
i18n-default-message="While typing a request, Console will make suggestions which you can then accept by hitting Enter/Tab.
These suggestions are made based on the request structure {asWellAs} your indices and types."
i18n-values="{ html_asWellAs: '<i>' + asWellAsFragmentText + '</i>' }"
>
</p>
<p>
<strong
i18n-id="console.welcomePage.quickTipsTitle"
i18n-default-message="A few quick tips, while I have your attention"
>
</strong>
</p>
<ul>
<li
i18n-id="console.welcomePage.quickTips.submitRequestDescription"
i18n-default-message="Submit requests to ES using the green triangle button."
></li>
<li
i18n-id="console.welcomePage.quickTips.useWrenchMenuDescription"
i18n-default-message="Use the wrench menu for other useful things."
></li>
<li
i18n-id="console.welcomePage.quickTips.cUrlFormatForRequestsDescription"
i18n-default-message="You can paste requests in cURL format and they will be translated to the Console syntax."
></li>
<li
i18n-id="console.welcomePage.quickTips.resizeEditorDescription"
i18n-default-message="You can resize the editor and output panes by dragging the separator between them."
></li>
<li
i18n-id="console.welcomePage.quickTips.keyboardShortcutsDescription"
i18n-default-message="Study the keyboard shortcuts under the Help button. Good stuff in there!"
></li>
</ul>
<button
type="button"
class="kuiButton kuiButton--primary"
data-test-subj="help-close-button"
ng-click="kbnTopNav.close()"
i18n-id="console.welcomePage.closeButtonLabel"
i18n-default-message="Get to work"
>
</button>
</div>

View file

@ -17,61 +17,57 @@
* under the License.
*/
import { KbnTopNavControllerProvider } from 'ui/kbn_top_nav/kbn_top_nav_controller';
import { i18n } from '@kbn/i18n';
import storage from '../storage';
export function SenseTopNavController(Private) {
const KbnTopNavController = Private(KbnTopNavControllerProvider);
import { IScope } from 'angular';
import { showSettingsModal } from './settings_show_modal';
const controller = new KbnTopNavController([
{
key: 'welcome',
label: i18n.translate('console.topNav.welcomeTabLabel', {
defaultMessage: 'Welcome'
}),
hideButton: true,
template: `<sense-welcome></sense-welcome>`,
testId: 'consoleWelcomeButton',
},
// help
import { showHelpPanel } from './help_show_panel';
export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) {
return [
{
key: 'history',
label: i18n.translate('console.topNav.historyTabLabel', {
defaultMessage: 'History'
defaultMessage: 'History',
}),
description: i18n.translate('console.topNav.historyTabDescription', {
defaultMessage: 'History',
}),
template: `<sense-history></sense-history>`,
run: () => {
toggleHistory();
},
testId: 'consoleHistoryButton',
},
{
key: 'settings',
label: i18n.translate('console.topNav.settingsTabLabel', {
defaultMessage: 'Settings'
defaultMessage: 'Settings',
}),
description: i18n.translate('console.topNav.settingsTabDescription', {
defaultMessage: 'Settings',
}),
template: `<sense-settings></sense-settings>`,
run: () => {
showSettingsModal();
},
testId: 'consoleSettingsButton',
},
{
key: 'help',
label: i18n.translate('console.topNav.helpTabLabel', {
defaultMessage: 'Help'
defaultMessage: 'Help',
}),
description: i18n.translate('console.topNav.helpTabDescription', {
defaultMessage: 'Help',
}),
template: `<sense-help></sense-help>`,
run: () => {
const hideHelpPanel = showHelpPanel();
$scope.$on('$destroy', () => {
hideHelpPanel();
});
},
testId: 'consoleHelpButton',
},
]);
if (storage.get('version_welcome_shown') !== '@@SENSE_REVISION') {
controller.open('welcome');
}
return controller;
];
}

View file

@ -17,26 +17,31 @@
* under the License.
*/
require('./sense_help_example');
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nContext } from 'ui/i18n';
import { HelpPanel } from '../components/help_panel';
import { i18n } from '@kbn/i18n';
import template from './welcome.html';
let isOpen = false;
const storage = require('../storage');
export function showHelpPanel(): () => void {
const onClose = () => {
if (!container) return;
ReactDOM.unmountComponentAtNode(container);
isOpen = false;
};
require('ui/modules')
.get('app/sense')
.directive('senseWelcome', function () {
return {
restrict: 'E',
template,
link: function ($scope) {
$scope.$on('$destroy', function () {
storage.set('version_welcome_shown', '@@SENSE_REVISION');
});
$scope.asWellAsFragmentText = i18n.translate('console.welcomePage.supportedRequestFormatDescription.asWellAsFragmentText', {
defaultMessage: 'as well as'
});
},
};
});
const container = document.getElementById('consoleHelpPanel');
if (container && !isOpen) {
isOpen = true;
const element = (
<I18nContext>
<HelpPanel onClose={onClose} />
</I18nContext>
);
ReactDOM.render(element, container);
}
return onClose;
}

View file

@ -0,0 +1,86 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { I18nContext } from 'ui/i18n';
import React from 'react';
import ReactDOM from 'react-dom';
import { DevToolsSettingsModal } from '../components/settings_modal';
import { DevToolsSettings } from '../components/dev_tools_settings';
// @ts-ignore
import mappings from '../mappings';
// @ts-ignore
import { getCurrentSettings, updateSettings } from '../settings';
export function showSettingsModal() {
const container = document.getElementById('consoleSettingsModal');
const curSettings = getCurrentSettings();
const refreshAutocompleteSettings = () => {
mappings.retrieveAutoCompleteInfo();
};
const closeModal = () => {
if (!container) return;
ReactDOM.unmountComponentAtNode(container);
container.innerHTML = '';
};
const getAutocompleteDiff = (newSettings: DevToolsSettings, prevSettings: DevToolsSettings) => {
return Object.keys(newSettings.autocomplete).filter(key => {
// @ts-ignore
return prevSettings.autocomplete[key] !== newSettings.autocomplete[key];
});
};
const fetchAutocompleteSettingsIfNeeded = (
newSettings: DevToolsSettings,
prevSettings: DevToolsSettings
) => {
// We'll only retrieve settings if polling is on.
const isPollingChanged = prevSettings.polling !== newSettings.polling;
if (newSettings.polling) {
const autocompleteDiff = getAutocompleteDiff(newSettings, prevSettings);
if (autocompleteDiff.length > 0) {
mappings.retrieveAutoCompleteInfo(newSettings.autocomplete);
} else if (isPollingChanged) {
mappings.retrieveAutoCompleteInfo();
}
}
};
const onSave = async (newSettings: DevToolsSettings) => {
const prevSettings = getCurrentSettings();
updateSettings(newSettings);
fetchAutocompleteSettingsIfNeeded(newSettings, prevSettings);
closeModal();
};
const element = (
<I18nContext>
<DevToolsSettingsModal
settings={curSettings}
onSaveSettings={onSave}
onClose={closeModal}
refreshAutocompleteSettings={refreshAutocompleteSettings}
/>
</I18nContext>
);
ReactDOM.render(element, container);
}

View file

@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nContext } from 'ui/i18n';
import { WelcomePanel } from '../components/welcome_panel';
// @ts-ignore
import storage from '../storage';
let isOpen = false;
export function showWelcomePanel(): () => void {
const onClose = () => {
if (!container) return;
ReactDOM.unmountComponentAtNode(container);
isOpen = false;
};
const onDismiss = () => {
storage.set('version_welcome_shown', '@@SENSE_REVISION');
onClose();
};
const container = document.getElementById('consoleWelcomePanel');
if (container && !isOpen) {
isOpen = true;
const element = (
<I18nContext>
<WelcomePanel onDismiss={onDismiss} />
</I18nContext>
);
ReactDOM.render(element, container);
}
return onClose;
}

View file

@ -72,7 +72,7 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
let CURRENT_REQ_ID = 0;
function sendCurrentRequestToES() {
function sendCurrentRequestToES(addedToHistoryCb) {
const reqId = ++CURRENT_REQ_ID;
@ -139,6 +139,9 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
// we have someone on the other side. Add to history
history.addToHistory(esPath, esMethod, esData);
if (addedToHistoryCb) {
addedToHistoryCb();
}
let value = xhr.responseText;
const mode = modeForContentType(xhr.getAllResponseHeaders('Content-Type') || '');
@ -212,7 +215,7 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
input.commands.addCommand({
name: 'send to elasticsearch',
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: sendCurrentRequestToES
exec: () => sendCurrentRequestToES()
});
input.commands.addCommand({
name: 'open documentation',

View file

@ -734,7 +734,6 @@
"console.topNav.historyTabLabel": "履歴",
"console.topNav.settingsTabDescription": "設定",
"console.topNav.settingsTabLabel": "設定",
"console.topNav.welcomeTabLabel": "ようこそ",
"console.welcomePage.closeButtonLabel": "始めましょう",
"console.welcomePage.pageTitle": "コンソールへようこそ",
"console.welcomePage.quickIntroDescription": "コンソール UI は、エディターペイン (左) と応答ペイン (右) の 2 つのペインに分かれています。エディターでリクエストを入力し、Elasticsearch に送信します。結果が右側の応答ペインに表示されます。",
@ -745,8 +744,6 @@
"console.welcomePage.quickTips.submitRequestDescription": "緑の三角形のボタンをクリックして ES にリクエストを送信します。",
"console.welcomePage.quickTips.useWrenchMenuDescription": "レンチメニューで他の便利な機能が使えます。",
"console.welcomePage.quickTipsTitle": "今のうちにいくつか簡単なコツをお教えします",
"console.welcomePage.supportedRequestFormatDescription": "リクエストの入力中、コンソールが候補を提案するので、Enter/Tab を押して確定できます。これらの候補はリクエストの構造{asWellAs}インデックス、タイプに基づくものです。",
"console.welcomePage.supportedRequestFormatDescription.asWellAsFragmentText": "や",
"console.welcomePage.supportedRequestFormatTitle": "コンソールは cURL と同様に、コンパクトなフォーマットのリクエストを理解できます。",
"core.chrome.legacyBrowserWarning": "ご使用のブラウザが Kibana のセキュリティ要件を満たしていません。",
"core.euiBasicTable.selectAllRows": "すべての行を選択",

View file

@ -601,7 +601,6 @@
"console.topNav.historyTabLabel": "历史记录",
"console.topNav.settingsTabDescription": "设置",
"console.topNav.settingsTabLabel": "设置",
"console.topNav.welcomeTabLabel": "欢迎",
"console.welcomePage.closeButtonLabel": "开始工作",
"console.welcomePage.pageTitle": "欢迎使用 Console",
"console.welcomePage.quickIntroDescription": "Console UI 分成两个窗格:编辑器窗格(左)和响应窗格(右)。使用编辑器键入请求并将它们提交到 Elasticsearch。结果将显示在右侧的响应窗格中。",
@ -612,8 +611,6 @@
"console.welcomePage.quickTips.submitRequestDescription": "使用绿色三角按钮将请求提交到 ES。",
"console.welcomePage.quickTips.useWrenchMenuDescription": "使用扳手菜单执行其他有用的操作。",
"console.welcomePage.quickTipsTitle": "有几个需要您注意的有用提示",
"console.welcomePage.supportedRequestFormatDescription": "键入请求时Console 将提供建议,您可以通过按 Enter/Tab 键来接受建议。这些建议基于请求结构{asWellAs}您的索引和类型做出。",
"console.welcomePage.supportedRequestFormatDescription.asWellAsFragmentText": "以及",
"console.welcomePage.supportedRequestFormatTitle": "Console 理解紧凑格式的请求,类似于 cURL",
"data.query.queryBar.searchInputPlaceholder": "搜索……例如status:200 AND extension:PHP",
"data.query.queryBar.syntaxOptionsDescription": "我们的实验性自动完成功能及简单语法功能可以帮助您创建自己的查询。只需开始键入,便会看到与您的数据相关的匹配。请参阅{docsLink}的文档。",