mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Begin notifier removal * Remove remaining notifier traces * Remove dead translations * Remove Angular from config listener * Import angular-sanitize explicitly in map
This commit is contained in:
parent
73ab20ed80
commit
92d1e5f5df
31 changed files with 9 additions and 952 deletions
|
@ -61,7 +61,6 @@ This list of plugins is not guaranteed to work on your version of Kibana. Instea
|
|||
[float]
|
||||
=== Other
|
||||
* https://github.com/nreese/kibana-time-plugin[Time picker as a dashboard panel] Widget to view and edit the time range from within dashboards.
|
||||
* https://github.com/sw-jung/kibana_notification_center[Notification Center] (sw-jung) - for better experience of notifier toasts.
|
||||
|
||||
* https://github.com/Webiks/kibana-API.git[Kibana-API] (webiks) Exposes an API with Kibana functionality.
|
||||
Use it to create, edit and embed visualizations, and also to search inside an embedded dashboard.
|
||||
|
|
|
@ -45,7 +45,6 @@ const module = uiModules.get('apps/context', [
|
|||
'elasticsearch',
|
||||
'kibana',
|
||||
'kibana/config',
|
||||
'kibana/notify',
|
||||
'ngRoute',
|
||||
]);
|
||||
|
||||
|
|
|
@ -78,7 +78,6 @@ const fetchStatuses = {
|
|||
};
|
||||
|
||||
const app = uiModules.get('apps/discover', [
|
||||
'kibana/notify',
|
||||
'kibana/courier',
|
||||
'kibana/url',
|
||||
'kibana/index_patterns'
|
||||
|
|
|
@ -24,7 +24,6 @@ import { createLegacyClass } from 'ui/utils/legacy_class';
|
|||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
|
||||
const module = uiModules.get('discover/saved_searches', [
|
||||
'kibana/notify',
|
||||
'kibana/courier'
|
||||
]);
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@ import 'ui/notify';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { savedObjectManagementRegistry } from '../../management/saved_object_registry';
|
||||
const module = uiModules.get('discover/saved_searches', [
|
||||
'kibana/notify'
|
||||
]);
|
||||
const module = uiModules.get('discover/saved_searches');
|
||||
|
||||
// Register this service with the saved object registry so it can be
|
||||
// edited by the object editor.
|
||||
|
|
|
@ -28,7 +28,6 @@ import 'plugins/kibana/doc_viewer';
|
|||
import { getRootBreadcrumbs } from 'plugins/kibana/discover/breadcrumbs';
|
||||
|
||||
const app = uiModules.get('apps/doc', [
|
||||
'kibana/notify',
|
||||
'kibana/courier',
|
||||
'kibana/index_patterns'
|
||||
]);
|
||||
|
|
|
@ -106,7 +106,6 @@ uiRoutes
|
|||
|
||||
uiModules
|
||||
.get('app/visualize', [
|
||||
'kibana/notify',
|
||||
'kibana/url'
|
||||
])
|
||||
.directive('visualizeApp', function () {
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications
|
||||
list="notifList"
|
||||
></kbn-notifications>
|
||||
|
||||
<div id="globalBannerList"></div>
|
||||
|
||||
<div
|
||||
|
|
|
@ -25,7 +25,6 @@ import { uiModules } from '../../modules';
|
|||
import template from './kbn_chrome.html';
|
||||
|
||||
import {
|
||||
notify,
|
||||
GlobalBannerList,
|
||||
banners,
|
||||
} from '../../notify';
|
||||
|
@ -58,9 +57,6 @@ export function kbnChromeProvider(chrome, internals) {
|
|||
|
||||
controllerAs: 'chrome',
|
||||
controller($scope, $location, Private) {
|
||||
// Notifications
|
||||
$scope.notifList = notify._notifs;
|
||||
|
||||
$scope.getFirstPathSegment = () => {
|
||||
return $location.path().split('/')[1];
|
||||
};
|
||||
|
|
|
@ -1,114 +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.
|
||||
*/
|
||||
|
||||
import angular from 'angular';
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import 'plugins/kibana/discover/index';
|
||||
|
||||
|
||||
let $parentScope;
|
||||
|
||||
let $scope;
|
||||
|
||||
let $elem;
|
||||
|
||||
const init = function (text) {
|
||||
// Load the application
|
||||
ngMock.module('kibana');
|
||||
|
||||
// Create the scope
|
||||
ngMock.inject(function ($rootScope, $compile) {
|
||||
|
||||
// Give us a scope
|
||||
$parentScope = $rootScope;
|
||||
|
||||
// Create the element
|
||||
$elem = angular.element(
|
||||
'<kbn-truncated source="' + text + '" length="10"></kbn-truncated>'
|
||||
);
|
||||
|
||||
// And compile it
|
||||
$compile($elem)($parentScope);
|
||||
|
||||
// Fire a digest cycle
|
||||
$elem.scope().$digest();
|
||||
|
||||
// Grab the isolate scope so we can test it
|
||||
$scope = $elem.isolateScope();
|
||||
});
|
||||
};
|
||||
|
||||
function trimmed(text) {
|
||||
return text.trim().replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
describe('kbnTruncate directive', function () {
|
||||
|
||||
describe('long strings', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
init('some string of text over 10 characters');
|
||||
});
|
||||
|
||||
it('should trim long strings', function (done) {
|
||||
expect(trimmed($elem.text())).to.be('some … more');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have a link to see more text', function (done) {
|
||||
expect($elem.find('[ng-click="toggle()"]').text()).to.be('more');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should show more text if the link is clicked and less text if clicked again', function (done) {
|
||||
$scope.toggle();
|
||||
$scope.$digest();
|
||||
expect(trimmed($elem.text())).to.be('some string of text over 10 characters less');
|
||||
expect($elem.find('[ng-click="toggle()"]').text()).to.be('less');
|
||||
|
||||
$scope.toggle();
|
||||
$scope.$digest();
|
||||
expect(trimmed($elem.text())).to.be('some … more');
|
||||
expect($elem.find('[ng-click="toggle()"]').text()).to.be('more');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('short strings', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
init('short');
|
||||
});
|
||||
|
||||
it('should not trim short strings', function (done) {
|
||||
expect(trimmed($elem.text())).to.be('short');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not have a link', function (done) {
|
||||
expect($elem.find('[ng-click="toggle()"]').length).to.be(0);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,165 +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.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from '@kbn/expect';
|
||||
import sinon from 'sinon';
|
||||
import { Notifier } from '..';
|
||||
import { metadata } from 'ui/metadata';
|
||||
|
||||
describe('Notifier', function () {
|
||||
let $interval;
|
||||
let notifier;
|
||||
let params;
|
||||
const message = 'Oh, the humanity!';
|
||||
|
||||
beforeEach(function () {
|
||||
ngMock.module('kibana');
|
||||
|
||||
ngMock.inject(function (_$interval_) {
|
||||
$interval = _$interval_;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
params = { location: 'foo' };
|
||||
notifier = new Notifier(params);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Notifier.prototype._notifs.length = 0;
|
||||
});
|
||||
|
||||
describe('#constructor()', function () {
|
||||
it('sets #from from given location', function () {
|
||||
expect(notifier.from).to.equal(params.location);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#error', function () {
|
||||
testVersionInfo('error');
|
||||
|
||||
it('prepends location to message for content', function () {
|
||||
expect(notify('error').content).to.equal(params.location + ': ' + message);
|
||||
});
|
||||
|
||||
it('sets type to "danger"', function () {
|
||||
expect(notify('error').type).to.equal('danger');
|
||||
});
|
||||
|
||||
it('sets icon to "warning"', function () {
|
||||
expect(notify('error').icon).to.equal('warning');
|
||||
});
|
||||
|
||||
it('sets title to "Error"', function () {
|
||||
expect(notify('error').title).to.equal('Error');
|
||||
});
|
||||
|
||||
it('sets lifetime to 5 minutes', function () {
|
||||
expect(notify('error').lifetime).to.equal(300000);
|
||||
});
|
||||
|
||||
it('sets timeRemaining and decrements', function () {
|
||||
const notif = notify('error');
|
||||
|
||||
expect(notif.timeRemaining).to.equal(300);
|
||||
$interval.flush(1000);
|
||||
expect(notif.timeRemaining).to.equal(299);
|
||||
});
|
||||
|
||||
it('closes notification on lifetime expiry', function () {
|
||||
const expectation = sinon.mock();
|
||||
const notif = notifier.error(message, expectation);
|
||||
|
||||
expectation.once();
|
||||
expectation.withExactArgs('ignore');
|
||||
|
||||
$interval.flush(300000);
|
||||
|
||||
expect(notif.timerId).to.be(undefined);
|
||||
});
|
||||
|
||||
it('allows canceling of timer', function () {
|
||||
const notif = notify('error');
|
||||
|
||||
expect(notif.timerId).to.not.be(undefined);
|
||||
notif.cancelTimer();
|
||||
|
||||
expect(notif.timerId).to.be(undefined);
|
||||
});
|
||||
|
||||
it('resets timer on addition to stack', function () {
|
||||
const notif = notify('error');
|
||||
|
||||
$interval.flush(100000);
|
||||
expect(notif.timeRemaining).to.equal(200);
|
||||
|
||||
notify('error');
|
||||
expect(notif.timeRemaining).to.equal(300);
|
||||
});
|
||||
|
||||
it('allows reporting', function () {
|
||||
const includesReport = _.includes(notify('error').actions, 'report');
|
||||
expect(includesReport).to.true;
|
||||
});
|
||||
|
||||
it('allows accepting', function () {
|
||||
const includesAccept = _.includes(notify('error').actions, 'accept');
|
||||
expect(includesAccept).to.true;
|
||||
});
|
||||
|
||||
it('includes stack', function () {
|
||||
expect(notify('error').stack).to.be.defined;
|
||||
});
|
||||
|
||||
it('has css class helper functions', function () {
|
||||
expect(notify('error').getIconClass()).to.equal('fa fa-warning');
|
||||
expect(notify('error').getButtonClass()).to.equal('kuiButton--danger');
|
||||
expect(notify('error').getAlertClassStack()).to.equal('kbnToast kbnToast-isStack kbnToast--danger');
|
||||
expect(notify('error').getAlertClass()).to.equal('kbnToast kbnToast--danger');
|
||||
expect(notify('error').getButtonGroupClass()).to.equal('kbnToast__controls');
|
||||
expect(notify('error').getToastMessageClass()).to.equal('kbnToast__message');
|
||||
});
|
||||
});
|
||||
|
||||
function notify(fnName, opts) {
|
||||
notifier[fnName](message, opts);
|
||||
return latestNotification();
|
||||
}
|
||||
|
||||
function latestNotification() {
|
||||
return _.last(notifier._notifs);
|
||||
}
|
||||
|
||||
function testVersionInfo(fnName) {
|
||||
describe('when version is configured', function () {
|
||||
it('adds version to notification', function () {
|
||||
const notification = notify(fnName);
|
||||
expect(notification.info.version).to.equal(metadata.version);
|
||||
});
|
||||
});
|
||||
describe('when build number is configured', function () {
|
||||
it('adds buildNum to notification', function () {
|
||||
const notification = notify(fnName);
|
||||
expect(notification.info.buildNum).to.equal(metadata.buildNum);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,2 +1 @@
|
|||
@import './banners/index';
|
||||
@import './partials/index';
|
||||
|
|
|
@ -1,66 +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.
|
||||
*/
|
||||
|
||||
import truncText from 'trunc-text';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import truncHTML from 'trunc-html';
|
||||
import { uiModules } from '../../modules';
|
||||
import truncatedTemplate from '../partials/truncated.html';
|
||||
import 'angular-sanitize';
|
||||
|
||||
const module = uiModules.get('kibana', ['ngSanitize']);
|
||||
|
||||
module.directive('kbnTruncated', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
source: '@',
|
||||
length: '@',
|
||||
isHtml: '@'
|
||||
},
|
||||
template: truncatedTemplate,
|
||||
link: function ($scope) {
|
||||
const source = $scope.source;
|
||||
const max = $scope.length;
|
||||
const truncated = $scope.isHtml
|
||||
? truncHTML(source, max).html
|
||||
: truncText(source, max);
|
||||
|
||||
$scope.content = truncated;
|
||||
|
||||
if (source === truncated) {
|
||||
return;
|
||||
}
|
||||
$scope.truncated = true;
|
||||
$scope.expanded = false;
|
||||
const showLessText = i18n.translate('common.ui.directives.truncated.showLessLinkText', {
|
||||
defaultMessage: 'less'
|
||||
});
|
||||
const showMoreText = i18n.translate('common.ui.directives.truncated.showMoreLinkText', {
|
||||
defaultMessage: 'more'
|
||||
});
|
||||
$scope.action = showMoreText;
|
||||
$scope.toggle = () => {
|
||||
$scope.expanded = !$scope.expanded;
|
||||
$scope.content = $scope.expanded ? source : truncated;
|
||||
$scope.action = $scope.expanded ? showLessText : showMoreText;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -17,8 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { notify } from './notify';
|
||||
export { Notifier } from './notifier';
|
||||
export { fatalError, addFatalErrorCallback } from './fatal_error';
|
||||
export { toastNotifications } from './toasts';
|
||||
export { GlobalBannerList, banners } from './banners';
|
||||
|
|
|
@ -1,219 +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.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { metadata } from '../metadata';
|
||||
import { formatMsg, formatStack } from './lib';
|
||||
import '../render_directive';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const notifs = [];
|
||||
|
||||
const { version, buildNum } = metadata;
|
||||
|
||||
function closeNotif(notif, cb = _.noop, key) {
|
||||
return function () {
|
||||
// this === notif
|
||||
const i = notifs.indexOf(notif);
|
||||
if (i !== -1) notifs.splice(i, 1);
|
||||
|
||||
cancelTimer(notif);
|
||||
cb(key);
|
||||
};
|
||||
}
|
||||
|
||||
function cancelTimer(notif) {
|
||||
if (notif.timerId) {
|
||||
Notifier.config.clearInterval(notif.timerId);
|
||||
notif.timerId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function timerCanceler(notif, cb = _.noop, key) {
|
||||
return function cancelNotifTimer() {
|
||||
cancelTimer(notif);
|
||||
cb(key);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a timer to update _timeRemaining_ on the notif at second
|
||||
* intervals and clears the notif once the notif _lifetime_ has been reached.
|
||||
*/
|
||||
function startNotifTimer(notif, cb) {
|
||||
const interval = 1000;
|
||||
|
||||
if (notif.lifetime === Infinity || notif.lifetime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
notif.timeRemaining = Math.floor(notif.lifetime / interval);
|
||||
|
||||
notif.timerId = Notifier.config.setInterval(
|
||||
function () {
|
||||
notif.timeRemaining -= 1;
|
||||
|
||||
if (notif.timeRemaining <= 0) {
|
||||
closeNotif(notif, cb, 'ignore')();
|
||||
}
|
||||
},
|
||||
interval,
|
||||
notif.timeRemaining
|
||||
);
|
||||
|
||||
notif.cancelTimer = timerCanceler(notif, cb);
|
||||
}
|
||||
|
||||
function restartNotifTimer(notif, cb) {
|
||||
cancelTimer(notif);
|
||||
startNotifTimer(notif, cb);
|
||||
}
|
||||
|
||||
const typeToButtonClassMap = {
|
||||
danger: 'kuiButton--danger', // NOTE: `error` type is internally named as `danger`
|
||||
info: 'kuiButton--secondary',
|
||||
};
|
||||
const typeToAlertClassMap = {
|
||||
danger: `kbnToast--danger`,
|
||||
info: `kbnToast--info`,
|
||||
};
|
||||
|
||||
function add(notif, cb) {
|
||||
_.set(notif, 'info.version', version);
|
||||
_.set(notif, 'info.buildNum', buildNum);
|
||||
|
||||
notif.clear = closeNotif(notif);
|
||||
|
||||
if (notif.actions) {
|
||||
notif.actions.forEach(function (action) {
|
||||
notif[action] = closeNotif(notif, cb, action);
|
||||
});
|
||||
} else if (notif.customActions) {
|
||||
// wrap all of the custom functions in a close
|
||||
notif.customActions = notif.customActions.map((action) => {
|
||||
return {
|
||||
key: action.text,
|
||||
dataTestSubj: action.dataTestSubj,
|
||||
callback: closeNotif(notif, action.callback, action.text),
|
||||
getButtonClass() {
|
||||
const buttonTypeClass = typeToButtonClassMap[notif.type];
|
||||
return `${buttonTypeClass}`;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
notif.count = (notif.count || 0) + 1;
|
||||
|
||||
notif.isTimed = function isTimed() {
|
||||
return notif.timerId ? true : false;
|
||||
};
|
||||
|
||||
// decorate the notification with helper functions for the template
|
||||
notif.getButtonClass = () => `${typeToButtonClassMap[notif.type]}`;
|
||||
notif.getAlertClassStack = () => `kbnToast kbnToast-isStack ${typeToAlertClassMap[notif.type]}`;
|
||||
notif.getIconClass = () => `fa fa-${notif.icon}`;
|
||||
notif.getToastMessageClass = () => 'kbnToast__message';
|
||||
notif.getAlertClass = () => `kbnToast ${typeToAlertClassMap[notif.type]}`;
|
||||
notif.getButtonGroupClass = () => 'kbnToast__controls';
|
||||
|
||||
let dup = null;
|
||||
if (notif.content) {
|
||||
dup = _.find(notifs, function (item) {
|
||||
return item.content === notif.content && item.lifetime === notif.lifetime;
|
||||
});
|
||||
}
|
||||
|
||||
if (dup) {
|
||||
dup.count += 1;
|
||||
dup.stacks = _.union(dup.stacks, [notif.stack]);
|
||||
|
||||
restartNotifTimer(dup, cb);
|
||||
|
||||
return dup;
|
||||
}
|
||||
|
||||
startNotifTimer(notif, cb);
|
||||
|
||||
notif.stacks = [notif.stack];
|
||||
notifs.push(notif);
|
||||
return notif;
|
||||
}
|
||||
|
||||
Notifier.prototype.add = add;
|
||||
|
||||
/**
|
||||
* Functionality to check that
|
||||
*/
|
||||
export function Notifier(opts) {
|
||||
const self = this;
|
||||
opts = opts || {};
|
||||
|
||||
// label type thing to say where notifications came from
|
||||
self.from = opts.location;
|
||||
|
||||
const notificationLevels = ['error'];
|
||||
|
||||
notificationLevels.forEach(function (m) {
|
||||
self[m] = _.bind(self[m], self);
|
||||
});
|
||||
}
|
||||
|
||||
Notifier.config = {
|
||||
bannerLifetime: 3000000,
|
||||
errorLifetime: 300000,
|
||||
infoLifetime: 5000,
|
||||
setInterval: window.setInterval,
|
||||
clearInterval: window.clearInterval,
|
||||
};
|
||||
|
||||
Notifier.applyConfig = function (config) {
|
||||
_.merge(Notifier.config, config);
|
||||
};
|
||||
|
||||
// simply a pointer to the global notif list
|
||||
Notifier.prototype._notifs = notifs;
|
||||
|
||||
const overridableOptions = ['lifetime', 'icon'];
|
||||
|
||||
/**
|
||||
* Alert the user of an error that occured
|
||||
* @param {Error|String} err
|
||||
* @param {Function} cb
|
||||
*/
|
||||
Notifier.prototype.error = function (err, opts, cb) {
|
||||
if (_.isFunction(opts)) {
|
||||
cb = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
const config = _.assign({
|
||||
type: 'danger',
|
||||
content: formatMsg(err, this.from),
|
||||
icon: 'warning',
|
||||
title: i18n.translate('common.ui.notify.toaster.errorTitle', {
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
lifetime: Notifier.config.errorLifetime,
|
||||
actions: ['report', 'accept'],
|
||||
stack: formatStack(err)
|
||||
}, _.pick(opts, overridableOptions));
|
||||
|
||||
return add(config, cb);
|
||||
};
|
|
@ -19,14 +19,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { MarkdownSimple } from 'ui/markdown';
|
||||
import { uiModules } from '../modules';
|
||||
import { metadata } from '../metadata';
|
||||
import chrome from '../chrome';
|
||||
import { fatalError } from './fatal_error';
|
||||
import { banners } from './banners';
|
||||
import { Notifier } from './notifier';
|
||||
import template from './partials/toaster.html';
|
||||
import './filters/markdown';
|
||||
import './directives/truncated';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
|
@ -34,60 +30,16 @@ import {
|
|||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const module = uiModules.get('kibana/notify');
|
||||
const config = chrome.getUiSettingsClient();
|
||||
|
||||
module.directive('kbnNotifications', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
list: '=list'
|
||||
},
|
||||
replace: true,
|
||||
template
|
||||
};
|
||||
});
|
||||
|
||||
export const notify = new Notifier();
|
||||
|
||||
module.factory('createNotifier', function () {
|
||||
return function (opts) {
|
||||
return new Notifier(opts);
|
||||
};
|
||||
});
|
||||
|
||||
module.factory('Notifier', function () {
|
||||
return Notifier;
|
||||
});
|
||||
|
||||
// teach Notifier how to use angular interval services
|
||||
module.run(function (config, $interval, $compile) {
|
||||
Notifier.applyConfig({
|
||||
setInterval: $interval,
|
||||
clearInterval: $interval.cancel
|
||||
});
|
||||
config.getUpdate$().subscribe(() => {
|
||||
applyConfig(config);
|
||||
Notifier.$compile = $compile;
|
||||
});
|
||||
|
||||
// if kibana is not included then the notify service can't
|
||||
// expect access to config (since it's dependent on kibana)
|
||||
if (!!metadata.kbnIndex) {
|
||||
require('ui/config');
|
||||
module.run(function (config) {
|
||||
config.watchAll(() => applyConfig(config));
|
||||
});
|
||||
}
|
||||
|
||||
let bannerId;
|
||||
let bannerTimeoutId;
|
||||
|
||||
function applyConfig(config) {
|
||||
Notifier.applyConfig({
|
||||
errorLifetime: config.get('notifications:lifetime:error'),
|
||||
warningLifetime: config.get('notifications:lifetime:warning'),
|
||||
infoLifetime: config.get('notifications:lifetime:info')
|
||||
});
|
||||
|
||||
// Show user-defined banner.
|
||||
const bannerContent = config.get('notifications:banner');
|
||||
const bannerLifetime = config.get('notifications:lifetime:banner');
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import './toaster';
|
|
@ -1,96 +0,0 @@
|
|||
// REDO Bootstrap alternatives to match EUI
|
||||
@import '@elastic/eui/src/components/call_out/variables';
|
||||
@import '@elastic/eui/src/components/call_out/mixins';
|
||||
|
||||
.kbnToaster__container {
|
||||
visibility: visible;
|
||||
width: 100%;
|
||||
|
||||
.kbnToaster {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Shouldn't look like a button.
|
||||
*/
|
||||
.kbnToaster__countdown {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.kbnToast {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $euiSizeXS $euiSize;
|
||||
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: $euiSizeXS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kbnToast__message {
|
||||
@include euiFontSizeS;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1 1 auto;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.kbnToast-isStack {
|
||||
@include euiFontSizeS;
|
||||
padding-bottom: $euiSizeS;
|
||||
|
||||
pre {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin: $euiSizeS 0;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.kbnToast__controls {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
$kbnToastTypes: (
|
||||
info: 'primary',
|
||||
warning: 'warning',
|
||||
danger: 'danger',
|
||||
success: 'success',
|
||||
);
|
||||
|
||||
@each $name, $color in $kbnToastTypes {
|
||||
$foreground: euiCallOutColor($color, 'foreground');
|
||||
|
||||
.kbnToast--#{$name} {
|
||||
background-color: euiCallOutColor($color, 'background');
|
||||
color: $foreground;
|
||||
|
||||
// Fix dark mode contrast by forcing button colors to use the same foreground color
|
||||
// Override hover/focus text color changes by using !important
|
||||
@if ($name = 'danger') {
|
||||
.kuiButton--danger,
|
||||
.kuiButton--danger:hover {
|
||||
color: $foreground !important;
|
||||
border-color: $foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@if ($name = 'info') {
|
||||
.kuiButton--secondary,
|
||||
.kuiButton--secondary:hover {
|
||||
color: $foreground !important;
|
||||
border-color: $foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
<div class="kbnToaster__container">
|
||||
<ul class="kbnToaster">
|
||||
<li ng-repeat="notif in list" kbn-toast notif="notif">
|
||||
<div ng-class="notif.getAlertClass()">
|
||||
<span ng-show="notif.count > 1" class="euiBadge euiBadge--default">
|
||||
<span class="euiBadge__content">
|
||||
<span class="euiBadge__text">{{ notif.count }}</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<i ng-class="notif.getIconClass()" tooltip="{{notif.title}}"></i>
|
||||
|
||||
<kbn-truncated
|
||||
ng-if="notif.content"
|
||||
source="{{notif.content | markdown}}"
|
||||
is-html="true"
|
||||
length="250"
|
||||
ng-class="notif.getToastMessageClass()"
|
||||
></kbn-truncated>
|
||||
|
||||
<render-directive
|
||||
ng-if="notif.directive"
|
||||
definition="notif.directive"
|
||||
notif="notif"
|
||||
ng-class="notif.getToastMessageClass()"
|
||||
></render-directive>
|
||||
|
||||
<div class="kuiButtonGroup" ng-class="notif.getButtonGroupClass()">
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.stack && !notif.showStack"
|
||||
class="kuiButton kuiButton--small"
|
||||
ng-class="notif.getButtonClass()"
|
||||
ng-click="notif.cancelTimer(); notif.showStack = true"
|
||||
i18n-id="common.ui.notify.toaster.moreInfoButtonLabel"
|
||||
i18n-default-message="More Info"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.stack && notif.showStack"
|
||||
class="kuiButton kuiButton--small"
|
||||
ng-class="notif.getButtonClass()"
|
||||
ng-click="notif.showStack = false"
|
||||
i18n-id="common.ui.notify.toaster.lessInfoButtonLabel"
|
||||
i18n-default-message="Less Info"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
data-test-subj="notifierDismissButton"
|
||||
type="button"
|
||||
ng-if="notif.accept"
|
||||
class="kuiButton kuiButton--small"
|
||||
ng-class="notif.getButtonClass()"
|
||||
ng-click="notif.accept()"
|
||||
i18n-id="common.ui.notify.toaster.okButtonLabel"
|
||||
i18n-default-message="OK"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.address"
|
||||
class="kuiButton kuiButton--small"
|
||||
ng-class="notif.getButtonClass()"
|
||||
ng-click="notif.address()"
|
||||
i18n-id="common.ui.notify.toaster.fixItButtonLabel"
|
||||
i18n-default-message="Fix it"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="kuiButton kuiButton--small"
|
||||
ng-repeat="action in notif.customActions"
|
||||
ng-class="action.getButtonClass()"
|
||||
ng-click="action.callback()"
|
||||
ng-bind="action.key"
|
||||
data-test-subj="{{action.dataTestSubj}}"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<!-- Countdown badge -->
|
||||
<button
|
||||
class="euiBadge euiBadge--default"
|
||||
type="button"
|
||||
ng-if="notif.isTimed()"
|
||||
class="kbnToaster__countdown"
|
||||
ng-click="notif.cancelTimer()"
|
||||
title="{{ ::'common.ui.notify.toaster.stopCountdownButtonTooltip' | i18n: { defaultMessage: 'Stop' } }}"
|
||||
>
|
||||
<span class="euiBadge__content">
|
||||
<span
|
||||
i18n-id="common.ui.notify.toaster.timeRemainingInSecondsLabel"
|
||||
i18n-default-message="{timeRemaining}s"
|
||||
i18n-values="{ timeRemaining: notif.timeRemaining }"
|
||||
></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-if="notif.stack && notif.showStack" ng-class="notif.getAlertClassStack()">
|
||||
<pre ng-repeat="stack in notif.stacks" ng-bind="stack"></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
<span ng-if="!isHtml">{{content}}</span>
|
||||
<span ng-if="isHtml" ng-bind-html="content"></span>
|
||||
<span ng-if="truncated">
|
||||
<a ng-click="toggle()">{{action}}</a>
|
||||
</span>
|
|
@ -41,7 +41,7 @@ describe('wrapRouteWithPrep fn', function () {
|
|||
const delayUserWork = opts.delayUserWork ? 50 : 0;
|
||||
|
||||
return function () {
|
||||
ngMock.module('kibana', 'kibana/notify');
|
||||
ngMock.module('kibana');
|
||||
let setupComplete = false;
|
||||
let userWorkComplete = false;
|
||||
let route;
|
||||
|
|
|
@ -22,7 +22,6 @@ import chrome from '../chrome';
|
|||
|
||||
import { parse as parseUrl } from 'url';
|
||||
import sinon from 'sinon';
|
||||
import { Notifier } from '../notify';
|
||||
import { metadata } from '../metadata';
|
||||
import { UiSettingsClient } from '../../../../core/public';
|
||||
|
||||
|
@ -67,15 +66,6 @@ function createStubUiSettings() {
|
|||
createStubUiSettings();
|
||||
sinon.stub(chrome, 'getUiSettingsClient').callsFake(() => stubUiSettings);
|
||||
|
||||
beforeEach(function () {
|
||||
// ensure that notifications are not left in the notifiers
|
||||
if (Notifier.prototype._notifs.length) {
|
||||
const notifs = JSON.stringify(Notifier.prototype._notifs);
|
||||
Notifier.prototype._notifs.length = 0;
|
||||
throw new Error('notifications were left in the notifier: ' + notifs);
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
createStubUiSettings();
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ import MarkdownIt from 'markdown-it';
|
|||
import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin';
|
||||
import { EMSClient } from '../../../../core_plugins/tile_map/common/ems_client';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import 'angular-sanitize';
|
||||
|
||||
const markdownIt = new MarkdownIt({
|
||||
html: false,
|
||||
|
@ -31,7 +32,7 @@ const markdownIt = new MarkdownIt({
|
|||
|
||||
const TMS_IN_YML_ID = 'TMS in config/kibana.yml';
|
||||
|
||||
uiModules.get('kibana')
|
||||
uiModules.get('kibana', ['ngSanitize'])
|
||||
.service('serviceSettings', function ($sanitize, mapConfig, tilemapsConfig, kbnVersion) {
|
||||
|
||||
const attributionFromConfig = $sanitize(markdownIt.render(tilemapsConfig.deprecated.config.options.attribution || ''));
|
||||
|
|
|
@ -1,49 +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.
|
||||
*/
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const log = getService('log');
|
||||
const PageObjects = getPageObjects(['monitoring', 'settings']);
|
||||
|
||||
describe('dismiss x-pack', function () {
|
||||
// Putting everything here in 'before' so it doesn't count as a test
|
||||
// since x-pack may or may not be installed. We just want the banner closed.
|
||||
before(function () {
|
||||
log.debug('check for X-Pack welcome, opt-out, and dismiss it');
|
||||
|
||||
// find class kbnToaster and see if there's any list items in it?
|
||||
return PageObjects.settings
|
||||
.navigateTo()
|
||||
.then(() => {
|
||||
return PageObjects.monitoring.getToasterContents();
|
||||
})
|
||||
.then(contents => {
|
||||
// Welcome to X-Pack!
|
||||
// Sharing your cluster statistics with us helps us improve. Your data is never shared with anyone. Not interested? Opt out here.
|
||||
// Dismiss
|
||||
log.debug('Toast banner contents = ' + contents);
|
||||
if (contents.includes('X-Pack')) {
|
||||
return PageObjects.monitoring.clickOptOut().then(() => {
|
||||
return PageObjects.monitoring.dismissWelcome();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -35,7 +35,6 @@ export default async function ({ readConfigFile }) {
|
|||
require.resolve('./apps/status_page'),
|
||||
require.resolve('./apps/timelion'),
|
||||
require.resolve('./apps/visualize'),
|
||||
require.resolve('./apps/xpack'),
|
||||
],
|
||||
pageObjects,
|
||||
services,
|
||||
|
|
|
@ -21,7 +21,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) {
|
|||
const config = getService('config');
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const find = getService('find');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const appsMenu = getService('appsMenu');
|
||||
const globalNav = getService('globalNav');
|
||||
|
@ -65,21 +64,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) {
|
|||
await this.awaitGlobalLoadingIndicatorHidden();
|
||||
}
|
||||
|
||||
async getToastMessage(findTimeout = defaultFindTimeout) {
|
||||
const toastMessage = await find.displayedByCssSelector(
|
||||
'kbn-truncated.kbnToast__message',
|
||||
findTimeout
|
||||
);
|
||||
const messageText = await toastMessage.getVisibleText();
|
||||
log.debug(`getToastMessage: ${messageText}`);
|
||||
return messageText;
|
||||
}
|
||||
|
||||
async clickToastOK() {
|
||||
log.debug('clickToastOK');
|
||||
await find.clickByCssSelector('button[ng-if="notif.accept"]');
|
||||
}
|
||||
|
||||
async waitUntilLoadingHasFinished() {
|
||||
try {
|
||||
await this.isGlobalLoadingIndicatorVisible();
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
export function MonitoringPageProvider({ getService }) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const find = getService('find');
|
||||
|
||||
class MonitoringPage {
|
||||
|
@ -27,15 +26,6 @@ export function MonitoringPageProvider({ getService }) {
|
|||
return await el.getVisibleText();
|
||||
}
|
||||
|
||||
dismissWelcome() {
|
||||
return testSubjects.click('notifierDismissButton');
|
||||
}
|
||||
|
||||
async getToasterContents() {
|
||||
const el = await find.byCssSelector('div.kbnToaster__container');
|
||||
return await el.getVisibleText();
|
||||
}
|
||||
|
||||
async clickOptOut() {
|
||||
return find.clickByLinkText('Opt out here');
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
/* hide unusable controls */
|
||||
.kbnGlobalNav,
|
||||
kbn-top-nav,
|
||||
.kbnToaster__container,
|
||||
filter-bar,
|
||||
::-webkit-scrollbar,
|
||||
.euiNavDrawer {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
/* hide unusable controls */
|
||||
.kbnGlobalNav,
|
||||
kbn-top-nav,
|
||||
.kbnToaster__container,
|
||||
filter-bar,
|
||||
::-webkit-scrollbar,
|
||||
.euiNavDrawer {
|
||||
|
|
|
@ -298,8 +298,6 @@
|
|||
"common.ui.directives.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド",
|
||||
"common.ui.directives.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド",
|
||||
"common.ui.directives.paginate.size.allDropDownOptionLabel": "すべて",
|
||||
"common.ui.directives.truncated.showLessLinkText": "隠す",
|
||||
"common.ui.directives.truncated.showMoreLinkText": "もっと",
|
||||
"common.ui.dualRangeControl.mustSetBothErrorMessage": "下と上の値の両方を設定する必要があります",
|
||||
"common.ui.dualRangeControl.outsideOfRangeErrorMessage": "値は {min} と {max} の間でなければなりません",
|
||||
"common.ui.dualRangeControl.upperValidErrorMessage": "上の値は下の値以上でなければなりません",
|
||||
|
@ -507,13 +505,6 @@
|
|||
"common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。",
|
||||
"common.ui.notify.toaster.errorMessage": "エラー: {errorMessage}\n {errorStack}",
|
||||
"common.ui.notify.toaster.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}",
|
||||
"common.ui.notify.toaster.errorTitle": "エラー",
|
||||
"common.ui.notify.toaster.fixItButtonLabel": "修正",
|
||||
"common.ui.notify.toaster.lessInfoButtonLabel": "情報を縮小",
|
||||
"common.ui.notify.toaster.moreInfoButtonLabel": "情報を拡張",
|
||||
"common.ui.notify.toaster.okButtonLabel": "OK",
|
||||
"common.ui.notify.toaster.stopCountdownButtonTooltip": "停止",
|
||||
"common.ui.notify.toaster.timeRemainingInSecondsLabel": "{timeRemaining}s",
|
||||
"common.ui.notify.toaster.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。",
|
||||
"common.ui.paginateControls.pageSizeLabel": "ページサイズ",
|
||||
"common.ui.paginateControls.scrollTopButtonLabel": "最上部に移動",
|
||||
|
@ -10723,4 +10714,4 @@
|
|||
"xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "ログテキストが必要です。",
|
||||
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -298,8 +298,6 @@
|
|||
"common.ui.directives.fieldNameIcons.stringFieldAriaLabel": "字符串字段",
|
||||
"common.ui.directives.fieldNameIcons.unknownFieldAriaLabel": "未知字段",
|
||||
"common.ui.directives.paginate.size.allDropDownOptionLabel": "全部",
|
||||
"common.ui.directives.truncated.showLessLinkText": "更少",
|
||||
"common.ui.directives.truncated.showMoreLinkText": "更多",
|
||||
"common.ui.dualRangeControl.mustSetBothErrorMessage": "下限值和上限值都须设置",
|
||||
"common.ui.dualRangeControl.outsideOfRangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内",
|
||||
"common.ui.dualRangeControl.upperValidErrorMessage": "上限值必须大于或等于下限值",
|
||||
|
@ -507,13 +505,6 @@
|
|||
"common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。",
|
||||
"common.ui.notify.toaster.errorMessage": "错误:{errorMessage}\n {errorStack}",
|
||||
"common.ui.notify.toaster.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}",
|
||||
"common.ui.notify.toaster.errorTitle": "错误",
|
||||
"common.ui.notify.toaster.fixItButtonLabel": "解决",
|
||||
"common.ui.notify.toaster.lessInfoButtonLabel": "更少信息",
|
||||
"common.ui.notify.toaster.moreInfoButtonLabel": "更多信息",
|
||||
"common.ui.notify.toaster.okButtonLabel": "确定",
|
||||
"common.ui.notify.toaster.stopCountdownButtonTooltip": "停止",
|
||||
"common.ui.notify.toaster.timeRemainingInSecondsLabel": "{timeRemaining} 秒",
|
||||
"common.ui.notify.toaster.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。",
|
||||
"common.ui.paginateControls.pageSizeLabel": "页面大小",
|
||||
"common.ui.paginateControls.scrollTopButtonLabel": "滚动至顶部",
|
||||
|
@ -10723,4 +10714,4 @@
|
|||
"xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "“日志文本”必填。",
|
||||
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue