mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
parent
955d709d43
commit
d6fb5ce920
59 changed files with 623 additions and 506 deletions
|
@ -4,6 +4,7 @@
|
|||
|
||||
## ChromeStart interface
|
||||
|
||||
ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
@ -32,6 +33,7 @@ export interface ChromeStart
|
|||
| [getIsCollapsed$()](./kibana-plugin-public.chromestart.getiscollapsed$.md) | Get an observable of the current collapsed state of the chrome. |
|
||||
| [getIsVisible$()](./kibana-plugin-public.chromestart.getisvisible$.md) | Get an observable of the current visibility state of the chrome. |
|
||||
| [removeApplicationClass(className)](./kibana-plugin-public.chromestart.removeapplicationclass.md) | Remove a className added with <code>addApplicationClass()</code>. If className is unknown it is ignored. |
|
||||
| [setAppTitle(appTitle)](./kibana-plugin-public.chromestart.setapptitle.md) | Sets the current app's title |
|
||||
| [setBadge(badge)](./kibana-plugin-public.chromestart.setbadge.md) | Override the current badge |
|
||||
| [setBrand(brand)](./kibana-plugin-public.chromestart.setbrand.md) | Set the brand configuration. |
|
||||
| [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs |
|
||||
|
@ -39,3 +41,28 @@ export interface ChromeStart
|
|||
| [setIsCollapsed(isCollapsed)](./kibana-plugin-public.chromestart.setiscollapsed.md) | Set the collapsed state of the chrome navigation. |
|
||||
| [setIsVisible(isVisible)](./kibana-plugin-public.chromestart.setisvisible.md) | Set the temporary visibility for the chrome. This does nothing if the chrome is hidden by default and should be used to hide the chrome for things like full-screen modes with an exit button. |
|
||||
|
||||
## Remarks
|
||||
|
||||
While ChromeStart exposes many APIs, they should be used sparingly and the developer should understand how they affect other plugins and applications.
|
||||
|
||||
## Example 1
|
||||
|
||||
How to add a recently accessed item to the sidebar:
|
||||
|
||||
```ts
|
||||
core.chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234');
|
||||
|
||||
```
|
||||
|
||||
## Example 2
|
||||
|
||||
How to set the help dropdown extension:
|
||||
|
||||
```tsx
|
||||
core.chrome.setHelpExtension(elem => {
|
||||
ReactDOM.render(<MyHelpComponent />, elem);
|
||||
return () => ReactDOM.unmountComponentAtNode(elem);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeStart](./kibana-plugin-public.chromestart.md) > [setAppTitle](./kibana-plugin-public.chromestart.setapptitle.md)
|
||||
|
||||
## ChromeStart.setAppTitle() method
|
||||
|
||||
Sets the current app's title
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
setAppTitle(appTitle: string): void;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| appTitle | <code>string</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
|
@ -33,7 +33,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. |
|
||||
| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. |
|
||||
| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | |
|
||||
| [ChromeStart](./kibana-plugin-public.chromestart.md) | |
|
||||
| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. |
|
||||
| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the <code>Plugin</code> setup lifecycle |
|
||||
| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the <code>Plugin</code> start lifecycle |
|
||||
| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | |
|
||||
|
|
1
src/core/public/chrome/_index.scss
Normal file
1
src/core/public/chrome/_index.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import './ui/index';
|
|
@ -22,11 +22,12 @@ import {
|
|||
ChromeBrand,
|
||||
ChromeBreadcrumb,
|
||||
ChromeService,
|
||||
ChromeStart,
|
||||
InternalChromeStart,
|
||||
} from './chrome_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const setupContract: jest.Mocked<ChromeStart> = {
|
||||
const startContract: jest.Mocked<InternalChromeStart> = {
|
||||
getComponent: jest.fn(),
|
||||
navLinks: {
|
||||
getNavLinks$: jest.fn(),
|
||||
has: jest.fn(),
|
||||
|
@ -48,6 +49,7 @@ const createStartContractMock = () => {
|
|||
getLeft$: jest.fn(),
|
||||
getRight$: jest.fn(),
|
||||
},
|
||||
setAppTitle: jest.fn(),
|
||||
setBrand: jest.fn(),
|
||||
getBrand$: jest.fn(),
|
||||
setIsVisible: jest.fn(),
|
||||
|
@ -64,14 +66,14 @@ const createStartContractMock = () => {
|
|||
getHelpExtension$: jest.fn(),
|
||||
setHelpExtension: jest.fn(),
|
||||
};
|
||||
setupContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand));
|
||||
setupContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
|
||||
setupContract.getIsCollapsed$.mockReturnValue(new BehaviorSubject(false));
|
||||
setupContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name']));
|
||||
setupContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge));
|
||||
setupContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb]));
|
||||
setupContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
|
||||
return setupContract;
|
||||
startContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand));
|
||||
startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
|
||||
startContract.getIsCollapsed$.mockReturnValue(new BehaviorSubject(false));
|
||||
startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name']));
|
||||
startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge));
|
||||
startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb]));
|
||||
startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type ChromeServiceContract = PublicMethodsOf<ChromeService>;
|
||||
|
|
|
@ -19,12 +19,15 @@
|
|||
|
||||
import * as Rx from 'rxjs';
|
||||
import { toArray } from 'rxjs/operators';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { applicationServiceMock } from '../application/application_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { notificationServiceMock } from '../notifications/notifications_service.mock';
|
||||
import { ChromeService } from './chrome_service';
|
||||
import { docLinksServiceMock } from '../doc_links/doc_links_service.mock';
|
||||
|
||||
const store = new Map();
|
||||
(window as any).localStorage = {
|
||||
|
@ -33,9 +36,10 @@ const store = new Map();
|
|||
removeItem: (key: string) => store.delete(String(key)),
|
||||
};
|
||||
|
||||
function defaultStartDeps(): any {
|
||||
function defaultStartDeps() {
|
||||
return {
|
||||
application: applicationServiceMock.createStartContract(),
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
http: httpServiceMock.createStartContract(),
|
||||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
|
@ -77,6 +81,16 @@ Array [
|
|||
expect(startDeps.notifications.toasts.addWarning).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('getComponent', () => {
|
||||
it('returns a renderable React component', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
// Have to do some fanagling to get the type system and enzyme to accept this.
|
||||
// Don't capture the snapshot because it's 600+ lines long.
|
||||
expect(shallow(React.createElement(() => start.getComponent()))).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('brand', () => {
|
||||
it('updates/emits the brand as it changes', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import * as Url from 'url';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { IconType } from '@elastic/eui';
|
||||
|
||||
import { InjectedMetadataStart } from '../injected_metadata';
|
||||
|
@ -32,6 +33,8 @@ import { HttpStart } from '../http';
|
|||
import { ChromeNavLinks, NavLinksService } from './nav_links';
|
||||
import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed';
|
||||
import { NavControlsService, ChromeNavControls } from './nav_controls';
|
||||
import { LoadingIndicator, Header } from './ui';
|
||||
import { DocLinksStart } from '../doc_links';
|
||||
|
||||
export { ChromeNavControls, ChromeRecentlyAccessed };
|
||||
|
||||
|
@ -71,6 +74,7 @@ interface ConstructorParams {
|
|||
|
||||
interface StartDeps {
|
||||
application: ApplicationStart;
|
||||
docLinks: DocLinksStart;
|
||||
http: HttpStart;
|
||||
injectedMetadata: InjectedMetadataStart;
|
||||
notifications: NotificationsStart;
|
||||
|
@ -90,12 +94,14 @@ export class ChromeService {
|
|||
|
||||
public async start({
|
||||
application,
|
||||
docLinks,
|
||||
http,
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
}: StartDeps): Promise<ChromeStart> {
|
||||
}: StartDeps): Promise<InternalChromeStart> {
|
||||
const FORCE_HIDDEN = isEmbedParamInHash();
|
||||
|
||||
const appTitle$ = new BehaviorSubject<string>('Kibana');
|
||||
const brand$ = new BehaviorSubject<ChromeBrand>({});
|
||||
const isVisible$ = new BehaviorSubject(true);
|
||||
const isCollapsed$ = new BehaviorSubject(!!localStorage.getItem(IS_COLLAPSED_KEY));
|
||||
|
@ -104,6 +110,10 @@ export class ChromeService {
|
|||
const breadcrumbs$ = new BehaviorSubject<ChromeBreadcrumb[]>([]);
|
||||
const badge$ = new BehaviorSubject<ChromeBadge | undefined>(undefined);
|
||||
|
||||
const navControls = this.navControls.start();
|
||||
const navLinks = this.navLinks.start({ application, http });
|
||||
const recentlyAccessed = await this.recentlyAccessed.start({ http });
|
||||
|
||||
if (!this.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) {
|
||||
notifications.toasts.addWarning(
|
||||
i18n.translate('core.chrome.legacyBrowserWarning', {
|
||||
|
@ -113,9 +123,39 @@ export class ChromeService {
|
|||
}
|
||||
|
||||
return {
|
||||
navControls: this.navControls.start(),
|
||||
navLinks: this.navLinks.start({ application, http }),
|
||||
recentlyAccessed: await this.recentlyAccessed.start({ http }),
|
||||
navControls,
|
||||
navLinks,
|
||||
recentlyAccessed,
|
||||
|
||||
getComponent: () => (
|
||||
<React.Fragment>
|
||||
<LoadingIndicator loadingCount$={http.getLoadingCount$()} />
|
||||
|
||||
<div className="header-global-wrapper hide-for-sharing" data-test-subj="headerGlobalNav">
|
||||
<Header
|
||||
appTitle$={appTitle$.pipe(takeUntil(this.stop$))}
|
||||
badge$={badge$.pipe(takeUntil(this.stop$))}
|
||||
basePath={http.basePath}
|
||||
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
|
||||
kibanaDocLink={docLinks.links.kibana}
|
||||
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
homeHref={http.basePath.prepend('/app/kibana#/home')}
|
||||
isVisible$={isVisible$.pipe(
|
||||
map(visibility => (FORCE_HIDDEN ? false : visibility)),
|
||||
takeUntil(this.stop$)
|
||||
)}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
navLinks$={navLinks.getNavLinks$()}
|
||||
recentlyAccessed$={recentlyAccessed.get$()}
|
||||
navControlsLeft$={navControls.getLeft$()}
|
||||
navControlsRight$={navControls.getRight$()}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
),
|
||||
|
||||
setAppTitle: (appTitle: string) => appTitle$.next(appTitle),
|
||||
|
||||
getBrand$: () => brand$.pipe(takeUntil(this.stop$)),
|
||||
|
||||
|
@ -193,7 +233,32 @@ export class ChromeService {
|
|||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* ChromeStart allows plugins to customize the global chrome header UI and
|
||||
* enrich the UX with additional information about the current location of the
|
||||
* browser.
|
||||
*
|
||||
* @remarks
|
||||
* While ChromeStart exposes many APIs, they should be used sparingly and the
|
||||
* developer should understand how they affect other plugins and applications.
|
||||
*
|
||||
* @example
|
||||
* How to add a recently accessed item to the sidebar:
|
||||
* ```ts
|
||||
* core.chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234');
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* How to set the help dropdown extension:
|
||||
* ```tsx
|
||||
* core.chrome.setHelpExtension(elem => {
|
||||
* ReactDOM.render(<MyHelpComponent />, elem);
|
||||
* return () => ReactDOM.unmountComponentAtNode(elem);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ChromeStart {
|
||||
/** {@inheritdoc ChromeNavLinks} */
|
||||
navLinks: ChromeNavLinks;
|
||||
|
@ -202,6 +267,15 @@ export interface ChromeStart {
|
|||
/** {@inheritdoc ChromeRecentlyAccessed} */
|
||||
recentlyAccessed: ChromeRecentlyAccessed;
|
||||
|
||||
/**
|
||||
* Sets the current app's title
|
||||
*
|
||||
* @internalRemarks
|
||||
* This should be handled by the application service once it is in charge
|
||||
* of mounting applications.
|
||||
*/
|
||||
setAppTitle(appTitle: string): void;
|
||||
|
||||
/**
|
||||
* Get an observable of the current brand information.
|
||||
*/
|
||||
|
@ -294,3 +368,12 @@ export interface ChromeStart {
|
|||
*/
|
||||
setHelpExtension(helpExtension?: ChromeHelpExtension): void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalChromeStart extends ChromeStart {
|
||||
/**
|
||||
* Used only by MountingService to render the header UI
|
||||
* @internal
|
||||
*/
|
||||
getComponent(): JSX.Element;
|
||||
}
|
|
@ -22,6 +22,7 @@ export {
|
|||
ChromeBreadcrumb,
|
||||
ChromeService,
|
||||
ChromeStart,
|
||||
InternalChromeStart,
|
||||
ChromeBrand,
|
||||
ChromeHelpExtension,
|
||||
} from './chrome_service';
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { sortBy } from 'lodash';
|
||||
import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
|
||||
/** @public */
|
||||
|
@ -48,6 +48,10 @@ export interface ChromeNavControls {
|
|||
registerLeft(navControl: ChromeNavControl): void;
|
||||
/** Register a nav control to be presented on the right side of the chrome header. */
|
||||
registerRight(navControl: ChromeNavControl): void;
|
||||
/** @internal */
|
||||
getLeft$(): Observable<ChromeNavControl[]>;
|
||||
/** @internal */
|
||||
getRight$(): Observable<ChromeNavControl[]>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
|
2
src/core/public/chrome/ui/_index.scss
Normal file
2
src/core/public/chrome/ui/_index.scss
Normal file
|
@ -0,0 +1,2 @@
|
|||
@import './header/index';
|
||||
@import './loading_indicator';
|
|
@ -1,3 +1,7 @@
|
|||
$kbnLoadingIndicatorBackgroundSize: $euiSizeXXL * 10;
|
||||
$kbnLoadingIndicatorColor1: tint($euiColorAccent, 15%);
|
||||
$kbnLoadingIndicatorColor2: tint($euiColorAccent, 60%);
|
||||
|
||||
/**
|
||||
* 1. Position this loader on top of the content.
|
||||
* 2. Make sure indicator isn't wider than the screen.
|
|
@ -41,8 +41,6 @@ import {
|
|||
// @ts-ignore
|
||||
EuiImage,
|
||||
// @ts-ignore
|
||||
EuiListGroupItem,
|
||||
// @ts-ignore
|
||||
EuiNavDrawer,
|
||||
// @ts-ignore
|
||||
EuiNavDrawerGroup,
|
||||
|
@ -52,9 +50,6 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import chrome from 'ui/chrome';
|
||||
import { HelpExtension } from 'ui/chrome';
|
||||
import { relativeToAbsolute } from 'ui/url/relative_to_absolute';
|
||||
|
||||
import { HeaderBadge } from './header_badge';
|
||||
import { HeaderBreadcrumbs } from './header_breadcrumbs';
|
||||
|
@ -67,24 +62,43 @@ import {
|
|||
ChromeNavLink,
|
||||
ChromeRecentlyAccessedHistoryItem,
|
||||
ChromeNavControl,
|
||||
} from '../../../../../../../core/public';
|
||||
} from '../..';
|
||||
import { HttpStart } from '../../../http';
|
||||
import { ChromeHelpExtension } from '../../chrome_service';
|
||||
|
||||
// Providing a buffer between the limit and the cut off index
|
||||
// protects from truncating just the last couple (6) characters
|
||||
const TRUNCATE_LIMIT: number = 64;
|
||||
const TRUNCATE_AT: number = 58;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - a relative or root relative url. If a relative path is given then the
|
||||
* absolute url returned will depend on the current page where this function is called from. For example
|
||||
* if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get
|
||||
* back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that
|
||||
* starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart".
|
||||
* @return {string} the relative url transformed into an absolute url
|
||||
*/
|
||||
function relativeToAbsolute(url: string) {
|
||||
// convert all link urls to absolute urls
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', url);
|
||||
return a.href;
|
||||
}
|
||||
|
||||
function extendRecentlyAccessedHistoryItem(
|
||||
navLinks: ChromeNavLink[],
|
||||
recentlyAccessed: ChromeRecentlyAccessedHistoryItem
|
||||
recentlyAccessed: ChromeRecentlyAccessedHistoryItem,
|
||||
basePath: HttpStart['basePath']
|
||||
) {
|
||||
const href = relativeToAbsolute(chrome.addBasePath(recentlyAccessed.link));
|
||||
const href = relativeToAbsolute(basePath.prepend(recentlyAccessed.link));
|
||||
const navLink = navLinks.find(nl => href.startsWith(nl.subUrlBase || nl.baseUrl));
|
||||
|
||||
let titleAndAriaLabel = recentlyAccessed.label;
|
||||
if (navLink) {
|
||||
const objectTypeForAriaAppendix = navLink.title;
|
||||
titleAndAriaLabel = i18n.translate('common.ui.recentLinks.linkItem.screenReaderLabel', {
|
||||
titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', {
|
||||
defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}',
|
||||
values: {
|
||||
recentlyAccessedItemLinklabel: recentlyAccessed.label,
|
||||
|
@ -131,22 +145,28 @@ function truncateRecentItemLabel(label: string): string {
|
|||
return label;
|
||||
}
|
||||
|
||||
export type HeaderProps = Pick<Props, Exclude<keyof Props, 'intl'>>;
|
||||
|
||||
interface Props {
|
||||
appTitle?: string;
|
||||
kibanaVersion: string;
|
||||
appTitle$: Rx.Observable<string>;
|
||||
badge$: Rx.Observable<ChromeBadge | undefined>;
|
||||
breadcrumbs$: Rx.Observable<ChromeBreadcrumb[]>;
|
||||
homeHref: string;
|
||||
isVisible$: Rx.Observable<boolean>;
|
||||
kibanaDocLink: string;
|
||||
navLinks$: Rx.Observable<ChromeNavLink[]>;
|
||||
recentlyAccessed$: Rx.Observable<ChromeRecentlyAccessedHistoryItem[]>;
|
||||
forceAppSwitcherNavigation$: Rx.Observable<boolean>;
|
||||
helpExtension$: Rx.Observable<HelpExtension>;
|
||||
helpExtension$: Rx.Observable<ChromeHelpExtension>;
|
||||
navControlsLeft$: Rx.Observable<ReadonlyArray<ChromeNavControl>>;
|
||||
navControlsRight$: Rx.Observable<ReadonlyArray<ChromeNavControl>>;
|
||||
intl: InjectedIntl;
|
||||
basePath: HttpStart['basePath'];
|
||||
}
|
||||
|
||||
interface State {
|
||||
appTitle: string;
|
||||
isVisible: boolean;
|
||||
navLinks: ReadonlyArray<ReturnType<typeof extendNavLink>>;
|
||||
recentlyAccessed: ReadonlyArray<ReturnType<typeof extendRecentlyAccessedHistoryItem>>;
|
||||
|
@ -163,6 +183,7 @@ class HeaderUI extends Component<Props, State> {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
appTitle: 'Kibana',
|
||||
isVisible: true,
|
||||
navLinks: [],
|
||||
recentlyAccessed: [],
|
||||
|
@ -174,27 +195,29 @@ class HeaderUI extends Component<Props, State> {
|
|||
|
||||
public componentDidMount() {
|
||||
this.subscription = Rx.combineLatest(
|
||||
this.props.appTitle$,
|
||||
this.props.isVisible$,
|
||||
this.props.forceAppSwitcherNavigation$,
|
||||
this.props.navLinks$,
|
||||
this.props.recentlyAccessed$,
|
||||
this.props.navControlsLeft$,
|
||||
this.props.navControlsRight$
|
||||
// Types for combineLatest only handle up to 6 inferred types so we combine these two separately.
|
||||
Rx.combineLatest(this.props.navControlsLeft$, this.props.navControlsRight$)
|
||||
).subscribe({
|
||||
next: ([
|
||||
appTitle,
|
||||
isVisible,
|
||||
forceNavigation,
|
||||
navLinks,
|
||||
recentlyAccessed,
|
||||
navControlsLeft,
|
||||
navControlsRight,
|
||||
[navControlsLeft, navControlsRight],
|
||||
]) => {
|
||||
this.setState({
|
||||
appTitle,
|
||||
isVisible,
|
||||
forceNavigation,
|
||||
navLinks: navLinks.map(navLink => extendNavLink(navLink)),
|
||||
recentlyAccessed: recentlyAccessed.map(ra =>
|
||||
extendRecentlyAccessedHistoryItem(navLinks, ra)
|
||||
extendRecentlyAccessedHistoryItem(navLinks, ra, this.props.basePath)
|
||||
),
|
||||
navControlsLeft,
|
||||
navControlsRight,
|
||||
|
@ -218,7 +241,7 @@ class HeaderUI extends Component<Props, State> {
|
|||
onClick={this.onNavClick}
|
||||
href={homeHref}
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'common.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel',
|
||||
id: 'core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel',
|
||||
defaultMessage: 'Go to home page',
|
||||
})}
|
||||
/>
|
||||
|
@ -237,8 +260,23 @@ class HeaderUI extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { appTitle, badge$, breadcrumbs$, helpExtension$, intl } = this.props;
|
||||
const { isVisible, navLinks, recentlyAccessed, navControlsLeft, navControlsRight } = this.state;
|
||||
const {
|
||||
badge$,
|
||||
basePath,
|
||||
breadcrumbs$,
|
||||
helpExtension$,
|
||||
intl,
|
||||
kibanaDocLink,
|
||||
kibanaVersion,
|
||||
} = this.props;
|
||||
const {
|
||||
appTitle,
|
||||
isVisible,
|
||||
navControlsLeft,
|
||||
navControlsRight,
|
||||
navLinks,
|
||||
recentlyAccessed,
|
||||
} = this.state;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
|
@ -259,7 +297,7 @@ class HeaderUI extends Component<Props, State> {
|
|||
size="s"
|
||||
alt=""
|
||||
aria-hidden={true}
|
||||
url={chrome.addBasePath(`/${navLink.icon}`)}
|
||||
url={basePath.prepend(`/${navLink.icon}`)}
|
||||
/>
|
||||
) : (
|
||||
undefined
|
||||
|
@ -270,14 +308,14 @@ class HeaderUI extends Component<Props, State> {
|
|||
const recentLinksArray = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsLabel',
|
||||
id: 'core.ui.chrome.sideGlobalNav.viewRecentItemsLabel',
|
||||
defaultMessage: 'Recently viewed',
|
||||
}),
|
||||
iconType: 'clock',
|
||||
isDisabled: recentlyAccessed.length > 0 ? false : true,
|
||||
flyoutMenu: {
|
||||
title: intl.formatMessage({
|
||||
id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle',
|
||||
id: 'core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle',
|
||||
defaultMessage: 'Recent items',
|
||||
}),
|
||||
listItems: recentlyAccessed.map(item => ({
|
||||
|
@ -310,7 +348,7 @@ class HeaderUI extends Component<Props, State> {
|
|||
|
||||
<EuiHeaderSection side="right">
|
||||
<EuiHeaderSectionItem>
|
||||
<HeaderHelpMenu helpExtension$={helpExtension$} />
|
||||
<HeaderHelpMenu {...{ helpExtension$, kibanaDocLink, kibanaVersion }} />
|
||||
</EuiHeaderSectionItem>
|
||||
|
||||
<HeaderNavControls side="right" navControls={navControlsRight} />
|
|
@ -21,6 +21,8 @@ import { EuiBetaBadge } from '@elastic/eui';
|
|||
import React, { Component } from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
|
||||
import { ChromeBadge } from '../../chrome_service';
|
||||
|
||||
interface Props {
|
||||
badge$: Rx.Observable<ChromeBadge | undefined>;
|
||||
}
|
||||
|
@ -29,8 +31,6 @@ interface State {
|
|||
badge: ChromeBadge | undefined;
|
||||
}
|
||||
|
||||
import { ChromeBadge } from '../../../../../../../core/public';
|
||||
|
||||
export class HeaderBadge extends Component<Props, State> {
|
||||
private subscription?: Rx.Subscription;
|
||||
|
|
@ -20,7 +20,8 @@
|
|||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
import { ChromeBreadcrumb } from '../../../../../../../core/public';
|
||||
|
||||
import { ChromeBreadcrumb } from '../../chrome_service';
|
||||
import { HeaderBreadcrumbs } from './header_breadcrumbs';
|
||||
|
||||
describe('HeaderBreadcrumbs', () => {
|
|
@ -25,8 +25,7 @@ import {
|
|||
// @ts-ignore
|
||||
EuiHeaderBreadcrumbs,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ChromeBreadcrumb } from '../../../../../../../core/public';
|
||||
import { ChromeBreadcrumb } from '../../chrome_service';
|
||||
|
||||
interface Props {
|
||||
appTitle?: string;
|
|
@ -38,22 +38,21 @@ import {
|
|||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { HelpExtension } from 'ui/chrome';
|
||||
import { metadata } from '../../../../metadata';
|
||||
import { documentationLinks } from '../../../../documentation_links';
|
||||
|
||||
import { HeaderExtension } from './header_extension';
|
||||
import { ChromeHelpExtension } from '../../chrome_service';
|
||||
|
||||
interface Props {
|
||||
helpExtension$: Rx.Observable<HelpExtension>;
|
||||
helpExtension$: Rx.Observable<ChromeHelpExtension>;
|
||||
intl: InjectedIntl;
|
||||
kibanaVersion: string;
|
||||
useDefaultContent?: boolean;
|
||||
documentationLink?: string;
|
||||
kibanaDocLink: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isOpen: boolean;
|
||||
helpExtension?: HelpExtension;
|
||||
helpExtension?: ChromeHelpExtension;
|
||||
}
|
||||
|
||||
class HeaderHelpMenuUI extends Component<Props, State> {
|
||||
|
@ -86,7 +85,7 @@ class HeaderHelpMenuUI extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { intl, useDefaultContent, documentationLink } = this.props;
|
||||
const { intl, kibanaVersion, useDefaultContent, kibanaDocLink } = this.props;
|
||||
const { helpExtension } = this.state;
|
||||
|
||||
const defaultContent = useDefaultContent ? (
|
||||
|
@ -94,7 +93,7 @@ class HeaderHelpMenuUI extends Component<Props, State> {
|
|||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="common.ui.chrome.headerGlobalNav.helpMenuHelpDescription"
|
||||
id="core.ui.chrome.headerGlobalNav.helpMenuHelpDescription"
|
||||
defaultMessage="Get updates, information, and answers in our documentation."
|
||||
/>
|
||||
</p>
|
||||
|
@ -102,9 +101,9 @@ class HeaderHelpMenuUI extends Component<Props, State> {
|
|||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiButton iconType="popout" href={documentationLink} target="_blank">
|
||||
<EuiButton iconType="popout" href={kibanaDocLink} target="_blank">
|
||||
<FormattedMessage
|
||||
id="common.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation"
|
||||
id="core.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation"
|
||||
defaultMessage="Go to documentation"
|
||||
/>
|
||||
</EuiButton>
|
||||
|
@ -116,7 +115,7 @@ class HeaderHelpMenuUI extends Component<Props, State> {
|
|||
aria-expanded={this.state.isOpen}
|
||||
aria-haspopup="true"
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'common.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel',
|
||||
id: 'core.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel',
|
||||
defaultMessage: 'Help menu',
|
||||
})}
|
||||
onClick={this.onMenuButtonClick}
|
||||
|
@ -140,15 +139,15 @@ class HeaderHelpMenuUI extends Component<Props, State> {
|
|||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="common.ui.chrome.headerGlobalNav.helpMenuTitle"
|
||||
id="core.ui.chrome.headerGlobalNav.helpMenuTitle"
|
||||
defaultMessage="Help"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="chrHeaderHelpMenu__version">
|
||||
<FormattedMessage
|
||||
id="common.ui.chrome.headerGlobalNav.helpMenuVersion"
|
||||
id="core.ui.chrome.headerGlobalNav.helpMenuVersion"
|
||||
defaultMessage="v {version}"
|
||||
values={{ version: metadata.version }}
|
||||
values={{ version: kibanaVersion }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -179,6 +178,5 @@ class HeaderHelpMenuUI extends Component<Props, State> {
|
|||
export const HeaderHelpMenu = injectI18n(HeaderHelpMenuUI);
|
||||
|
||||
HeaderHelpMenu.defaultProps = {
|
||||
documentationLink: documentationLinks.kibana,
|
||||
useDefaultContent: true,
|
||||
};
|
|
@ -25,7 +25,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { HeaderExtension } from './header_extension';
|
||||
import { ChromeNavControl } from '../../../../../../../core/public';
|
||||
import { ChromeNavControl } from '../../nav_controls';
|
||||
|
||||
interface Props {
|
||||
navControls: ReadonlyArray<ChromeNavControl>;
|
|
@ -17,9 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiRegistry } from './_registry';
|
||||
|
||||
export const chromeNavControlsRegistry = uiRegistry({
|
||||
name: 'chromeNavControls',
|
||||
order: ['order']
|
||||
});
|
||||
export { Header, HeaderProps } from './header';
|
|
@ -17,16 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './header_global_nav';
|
||||
|
||||
export enum NavControlSide {
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
}
|
||||
|
||||
export interface NavControl {
|
||||
name: string;
|
||||
order: number;
|
||||
side: NavControlSide;
|
||||
render: (targetDomElement: HTMLDivElement) => (() => void);
|
||||
}
|
||||
export { LoadingIndicator } from './loading_indicator';
|
||||
export { Header } from './header';
|
|
@ -19,38 +19,19 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import chrome from 'ui/chrome';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { LoadingIndicator } from './loading_indicator';
|
||||
|
||||
jest.mock('ui/chrome', () => {
|
||||
return {
|
||||
loadingCount: {
|
||||
subscribe: jest.fn(() => {
|
||||
return () => {};
|
||||
})
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
chrome.loadingCount.subscribe.mockClear();
|
||||
});
|
||||
|
||||
describe('kbnLoadingIndicator', function () {
|
||||
it('is hidden by default', function () {
|
||||
const wrapper = shallow(<LoadingIndicator />);
|
||||
describe('kbnLoadingIndicator', () => {
|
||||
it('is hidden by default', () => {
|
||||
const wrapper = shallow(<LoadingIndicator loadingCount$={new BehaviorSubject(0)} />);
|
||||
expect(wrapper.prop('data-test-subj')).toBe('globalLoadingIndicator-hidden');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('is visible when loadingCount is > 0', () => {
|
||||
chrome.loadingCount.subscribe.mockImplementation((fn) => {
|
||||
fn(1);
|
||||
return () => {};
|
||||
});
|
||||
|
||||
const wrapper = shallow(<LoadingIndicator />);
|
||||
const wrapper = shallow(<LoadingIndicator loadingCount$={new BehaviorSubject(1)} />);
|
||||
expect(wrapper.prop('data-test-subj')).toBe('globalLoadingIndicator');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
|
@ -17,20 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import 'ngreact';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
import { HttpStart } from '../../http';
|
||||
|
||||
export interface LoadingIndicatorProps {
|
||||
loadingCount$: ReturnType<HttpStart['getLoadingCount$']>;
|
||||
}
|
||||
|
||||
export class LoadingIndicator extends React.Component<LoadingIndicatorProps, { visible: boolean }> {
|
||||
private loadingCountSubscription?: Subscription;
|
||||
|
||||
export class LoadingIndicator extends React.Component {
|
||||
state = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._unsub = chrome.loadingCount.subscribe(count => {
|
||||
this.loadingCountSubscription = this.props.loadingCount$.subscribe(count => {
|
||||
this.setState({
|
||||
visible: count > 0,
|
||||
});
|
||||
|
@ -38,8 +43,10 @@ export class LoadingIndicator extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._unsub();
|
||||
this._unsub = null;
|
||||
if (this.loadingCountSubscription) {
|
||||
this.loadingCountSubscription.unsubscribe();
|
||||
this.loadingCountSubscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -56,7 +63,3 @@ export class LoadingIndicator extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
uiModules
|
||||
.get('app/kibana', ['react'])
|
||||
.directive('kbnLoadingIndicator', reactDirective => reactDirective(LoadingIndicator));
|
|
@ -29,6 +29,7 @@ import { overlayServiceMock } from './overlays/overlay_service.mock';
|
|||
import { pluginsServiceMock } from './plugins/plugins_service.mock';
|
||||
import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
||||
import { docLinksServiceMock } from './doc_links/doc_links_service.mock';
|
||||
import { renderingServiceMock } from './rendering/rendering_service.mock';
|
||||
|
||||
export const MockLegacyPlatformService = legacyPlatformServiceMock.create();
|
||||
export const LegacyPlatformServiceConstructor = jest
|
||||
|
@ -113,3 +114,9 @@ export const DocLinksServiceConstructor = jest.fn().mockImplementation(() => Moc
|
|||
jest.doMock('./doc_links', () => ({
|
||||
DocLinksService: DocLinksServiceConstructor,
|
||||
}));
|
||||
|
||||
export const MockRenderingService = renderingServiceMock.create();
|
||||
export const RenderingServiceConstructor = jest.fn().mockImplementation(() => MockRenderingService);
|
||||
jest.doMock('./rendering', () => ({
|
||||
RenderingService: RenderingServiceConstructor,
|
||||
}));
|
||||
|
|
|
@ -39,6 +39,8 @@ import {
|
|||
UiSettingsServiceConstructor,
|
||||
MockApplicationService,
|
||||
MockDocLinksService,
|
||||
MockRenderingService,
|
||||
RenderingServiceConstructor,
|
||||
} from './core_system.test.mocks';
|
||||
|
||||
import { CoreSystem } from './core_system';
|
||||
|
@ -80,6 +82,7 @@ describe('constructor', () => {
|
|||
expect(UiSettingsServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(ChromeServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(OverlayServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(RenderingServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('passes injectedMetadata param to InjectedMetadataService', () => {
|
||||
|
@ -199,11 +202,13 @@ describe('#start()', () => {
|
|||
await core.start();
|
||||
}
|
||||
|
||||
it('clears the children of the rootDomElement and appends container for legacyPlatform and notifications', async () => {
|
||||
it('clears the children of the rootDomElement and appends container for rendering service with #kibana-body, notifications, overlays', async () => {
|
||||
const root = document.createElement('div');
|
||||
root.innerHTML = '<p>foo bar</p>';
|
||||
await startCore(root);
|
||||
expect(root.innerHTML).toBe('<div></div><div></div><div></div>');
|
||||
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||
`"<div id=\\"kibana-body\\"></div><div></div><div></div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it('calls application#start()', async () => {
|
||||
|
@ -255,6 +260,15 @@ describe('#start()', () => {
|
|||
await startCore();
|
||||
expect(MockOverlayService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls rendering#start()', async () => {
|
||||
await startCore();
|
||||
expect(MockRenderingService.start).toHaveBeenCalledTimes(1);
|
||||
expect(MockRenderingService.start).toHaveBeenCalledWith({
|
||||
chrome: expect.any(Object),
|
||||
targetDomElement: expect.any(HTMLElement),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop()', () => {
|
||||
|
@ -319,25 +333,44 @@ describe('#stop()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('LegacyPlatform targetDomElement', () => {
|
||||
it('only mounts the element when start, after setting up the legacyPlatformService', async () => {
|
||||
describe('RenderingService targetDomElement', () => {
|
||||
it('only mounts the element when start, after setting up the renderingService', async () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
const core = createCoreSystem({
|
||||
rootDomElement,
|
||||
});
|
||||
|
||||
let targetDomElementParentInStart: HTMLElement | null;
|
||||
MockLegacyPlatformService.start.mockImplementation(async ({ targetDomElement }) => {
|
||||
MockRenderingService.start.mockImplementation(({ targetDomElement }) => {
|
||||
targetDomElementParentInStart = targetDomElement.parentElement;
|
||||
return { legacyTargetDomElement: document.createElement('div') };
|
||||
});
|
||||
|
||||
// setting up the core system should mount the targetDomElement as a child of the rootDomElement
|
||||
// Starting the core system should pass the targetDomElement as a child of the rootDomElement
|
||||
await core.setup();
|
||||
await core.start();
|
||||
expect(targetDomElementParentInStart!).toBe(rootDomElement);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LegacyPlatformService targetDomElement', () => {
|
||||
it('only mounts the element when start, after setting up the legacyPlatformService', async () => {
|
||||
const core = createCoreSystem();
|
||||
|
||||
let targetDomElementInStart: HTMLElement | null;
|
||||
MockLegacyPlatformService.start.mockImplementation(({ targetDomElement }) => {
|
||||
targetDomElementInStart = targetDomElement;
|
||||
});
|
||||
|
||||
await core.setup();
|
||||
await core.start();
|
||||
// Starting the core system should pass the legacyTargetDomElement to the LegacyPlatformService
|
||||
const renderingLegacyTargetDomElement =
|
||||
MockRenderingService.start.mock.results[0].value.legacyTargetDomElement;
|
||||
expect(targetDomElementInStart!).toBe(renderingLegacyTargetDomElement);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Notifications targetDomElement', () => {
|
||||
it('only mounts the element when started, after setting up the notificationsService', async () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
|
@ -353,7 +386,7 @@ describe('Notifications targetDomElement', () => {
|
|||
}
|
||||
);
|
||||
|
||||
// setting up and starting the core system should mount the targetDomElement as a child of the rootDomElement
|
||||
// Starting the core system should pass the targetDomElement as a child of the rootDomElement
|
||||
await core.setup();
|
||||
await core.start();
|
||||
expect(targetDomElementParentInStart!).toBe(rootDomElement);
|
||||
|
|
|
@ -33,6 +33,7 @@ import { UiSettingsService } from './ui_settings';
|
|||
import { ApplicationService } from './application';
|
||||
import { mapToObject } from '../utils/';
|
||||
import { DocLinksService } from './doc_links';
|
||||
import { RenderingService } from './rendering';
|
||||
|
||||
interface Params {
|
||||
rootDomElement: HTMLElement;
|
||||
|
@ -67,6 +68,7 @@ export class CoreSystem {
|
|||
private readonly plugins: PluginsService;
|
||||
private readonly application: ApplicationService;
|
||||
private readonly docLinks: DocLinksService;
|
||||
private readonly rendering: RenderingService;
|
||||
|
||||
private readonly rootDomElement: HTMLElement;
|
||||
private fatalErrorsSetup: FatalErrorsSetup | null = null;
|
||||
|
@ -100,6 +102,7 @@ export class CoreSystem {
|
|||
this.application = new ApplicationService();
|
||||
this.chrome = new ChromeService({ browserSupportsCsp });
|
||||
this.docLinks = new DocLinksService();
|
||||
this.rendering = new RenderingService();
|
||||
|
||||
const core: CoreContext = {};
|
||||
this.plugins = new PluginsService(core);
|
||||
|
@ -157,15 +160,16 @@ export class CoreSystem {
|
|||
const i18n = await this.i18n.start();
|
||||
const application = await this.application.start({ injectedMetadata });
|
||||
|
||||
const coreUiTargetDomElement = document.createElement('div');
|
||||
coreUiTargetDomElement.id = 'kibana-body';
|
||||
const notificationsTargetDomElement = document.createElement('div');
|
||||
const overlayTargetDomElement = document.createElement('div');
|
||||
const legacyPlatformTargetDomElement = document.createElement('div');
|
||||
|
||||
// ensure the rootDomElement is empty
|
||||
this.rootDomElement.textContent = '';
|
||||
this.rootDomElement.classList.add('coreSystemRootDomElement');
|
||||
this.rootDomElement.appendChild(coreUiTargetDomElement);
|
||||
this.rootDomElement.appendChild(notificationsTargetDomElement);
|
||||
this.rootDomElement.appendChild(legacyPlatformTargetDomElement);
|
||||
this.rootDomElement.appendChild(overlayTargetDomElement);
|
||||
|
||||
const overlays = this.overlay.start({ i18n, targetDomElement: overlayTargetDomElement });
|
||||
|
@ -176,6 +180,7 @@ export class CoreSystem {
|
|||
});
|
||||
const chrome = await this.chrome.start({
|
||||
application,
|
||||
docLinks,
|
||||
http,
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
|
@ -195,10 +200,14 @@ export class CoreSystem {
|
|||
};
|
||||
|
||||
const plugins = await this.plugins.start(core);
|
||||
const rendering = this.rendering.start({
|
||||
chrome,
|
||||
targetDomElement: coreUiTargetDomElement,
|
||||
});
|
||||
await this.legacyPlatform.start({
|
||||
core,
|
||||
plugins: mapToObject(plugins.contracts),
|
||||
targetDomElement: legacyPlatformTargetDomElement,
|
||||
targetDomElement: rendering.legacyTargetDomElement,
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.fatalErrorsSetup) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import { HttpService } from './http_service';
|
||||
import { HttpSetup } from './types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
type ServiceSetupMockType = jest.Mocked<HttpSetup> & {
|
||||
basePath: jest.Mocked<HttpSetup['basePath']>;
|
||||
|
@ -39,7 +40,7 @@ const createServiceMock = (): ServiceSetupMockType => ({
|
|||
remove: jest.fn(),
|
||||
},
|
||||
addLoadingCount: jest.fn(),
|
||||
getLoadingCount$: jest.fn(),
|
||||
getLoadingCount$: jest.fn().mockReturnValue(new BehaviorSubject(0)),
|
||||
stop: jest.fn(),
|
||||
intercept: jest.fn(),
|
||||
removeAllInterceptors: jest.fn(),
|
||||
|
|
10
src/core/public/index.scss
Normal file
10
src/core/public/index.scss
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Functions need to be first, since we use them in our variables and mixin definitions
|
||||
@import '@elastic/eui/src/global_styling/functions/index';
|
||||
|
||||
// Variables come next, and are used in some mixins
|
||||
@import '@elastic/eui/src/global_styling/variables/index';
|
||||
|
||||
// Mixins provide generic code expansion through helpers
|
||||
@import '@elastic/eui/src/global_styling/mixins/index';
|
||||
|
||||
@import './chrome/index';
|
|
@ -22,20 +22,21 @@ import {
|
|||
NotificationsStart,
|
||||
} from './notifications_service';
|
||||
import { toastsServiceMock } from './toasts/toasts_service.mock';
|
||||
import { ToastsApi } from './toasts/toasts_api';
|
||||
|
||||
type DeeplyMocked<T> = { [P in keyof T]: jest.Mocked<T[P]> };
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
const setupContract: jest.Mocked<NotificationsSetup> = {
|
||||
const setupContract: DeeplyMocked<NotificationsSetup> = {
|
||||
// we have to suppress type errors until decide how to mock es6 class
|
||||
toasts: (toastsServiceMock.createSetupContract() as unknown) as ToastsApi,
|
||||
toasts: toastsServiceMock.createSetupContract(),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<NotificationsStart> = {
|
||||
const startContract: DeeplyMocked<NotificationsStart> = {
|
||||
// we have to suppress type errors until decide how to mock es6 class
|
||||
toasts: (toastsServiceMock.createStartContract() as unknown) as ToastsApi,
|
||||
toasts: toastsServiceMock.createStartContract(),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import { DiscoveredPlugin, PluginName } from '../../server';
|
||||
import { CoreContext } from '../core_system';
|
||||
import { PluginWrapper } from './plugin';
|
||||
|
@ -100,7 +102,7 @@ export function createPluginStartContext<
|
|||
},
|
||||
docLinks: deps.docLinks,
|
||||
http: deps.http,
|
||||
chrome: deps.chrome,
|
||||
chrome: omit(deps.chrome, 'getComponent'),
|
||||
i18n: deps.i18n,
|
||||
notifications: deps.notifications,
|
||||
overlays: deps.overlays,
|
||||
|
|
|
@ -96,6 +96,7 @@ beforeEach(() => {
|
|||
application: {
|
||||
capabilities: mockStartDeps.application.capabilities,
|
||||
},
|
||||
chrome: omit(mockStartDeps.chrome, 'getComponent'),
|
||||
};
|
||||
|
||||
// Reset these for each test.
|
||||
|
|
|
@ -85,6 +85,10 @@ export interface ChromeNavControl {
|
|||
|
||||
// @public
|
||||
export interface ChromeNavControls {
|
||||
// @internal (undocumented)
|
||||
getLeft$(): Observable<ChromeNavControl[]>;
|
||||
// @internal (undocumented)
|
||||
getRight$(): Observable<ChromeNavControl[]>;
|
||||
registerLeft(navControl: ChromeNavControl): void;
|
||||
registerRight(navControl: ChromeNavControl): void;
|
||||
}
|
||||
|
@ -139,7 +143,7 @@ export interface ChromeRecentlyAccessedHistoryItem {
|
|||
link: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export interface ChromeStart {
|
||||
addApplicationClass(className: string): void;
|
||||
getApplicationClasses$(): Observable<string[]>;
|
||||
|
@ -153,6 +157,7 @@ export interface ChromeStart {
|
|||
navLinks: ChromeNavLinks;
|
||||
recentlyAccessed: ChromeRecentlyAccessed;
|
||||
removeApplicationClass(className: string): void;
|
||||
setAppTitle(appTitle: string): void;
|
||||
setBadge(badge?: ChromeBadge): void;
|
||||
setBrand(brand: ChromeBrand): void;
|
||||
setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void;
|
||||
|
|
20
src/core/public/rendering/index.ts
Normal file
20
src/core/public/rendering/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { RenderingService, RenderingStart } from './rendering_service';
|
|
@ -17,29 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { chromeNavControlsRegistry } from '../../registry/chrome_nav_controls';
|
||||
import { uiModules } from '../../modules';
|
||||
import { RenderingStart, RenderingService } from './rendering_service';
|
||||
|
||||
export function kbnAppendChromeNavControls() {
|
||||
const createStartContractMock = () => {
|
||||
const setupContract: jest.Mocked<RenderingStart> = {
|
||||
legacyTargetDomElement: document.createElement('div'),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
.directive('kbnChromeAppendNavControls', function (Private) {
|
||||
return {
|
||||
template: function ($element) {
|
||||
const parts = [$element.html()];
|
||||
const controls = Private(chromeNavControlsRegistry);
|
||||
type RenderingServiceContract = PublicMethodsOf<RenderingService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<RenderingServiceContract> = {
|
||||
start: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
for (const control of controls.inOrder) {
|
||||
parts.unshift(
|
||||
`<!-- nav control ${control.name} -->`,
|
||||
control.template
|
||||
);
|
||||
}
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
export const renderingServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
66
src/core/public/rendering/rendering_service.test.tsx
Normal file
66
src/core/public/rendering/rendering_service.test.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { chromeServiceMock } from '../chrome/chrome_service.mock';
|
||||
import { RenderingService } from './rendering_service';
|
||||
|
||||
describe('RenderingService#start', () => {
|
||||
const getService = () => {
|
||||
const rendering = new RenderingService();
|
||||
const chrome = chromeServiceMock.createStartContract();
|
||||
chrome.getComponent.mockReturnValue(<div>Hello chrome!</div>);
|
||||
const targetDomElement = document.createElement('div');
|
||||
const start = rendering.start({ chrome, targetDomElement });
|
||||
return { start, targetDomElement };
|
||||
};
|
||||
|
||||
it('renders into provided DOM element', () => {
|
||||
const { targetDomElement } = getService();
|
||||
expect(targetDomElement).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="content"
|
||||
data-test-subj="kibanaChrome"
|
||||
>
|
||||
<div>
|
||||
Hello chrome!
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns a div for the legacy service to render into', () => {
|
||||
const {
|
||||
start: { legacyTargetDomElement },
|
||||
targetDomElement,
|
||||
} = getService();
|
||||
legacyTargetDomElement.innerHTML = '<span id="legacy">Hello legacy!</span>';
|
||||
expect(targetDomElement.querySelector('#legacy')).toMatchInlineSnapshot(`
|
||||
<span
|
||||
id="legacy"
|
||||
>
|
||||
Hello legacy!
|
||||
</span>
|
||||
`);
|
||||
});
|
||||
});
|
66
src/core/public/rendering/rendering_service.tsx
Normal file
66
src/core/public/rendering/rendering_service.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { I18nProvider } from '@kbn/i18n/react';
|
||||
|
||||
import { InternalChromeStart } from '../chrome';
|
||||
|
||||
interface StartDeps {
|
||||
chrome: InternalChromeStart;
|
||||
targetDomElement: HTMLDivElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all Core UI in a single React tree.
|
||||
*
|
||||
* @internalRemarks Currently this only renders Chrome UI. Notifications and
|
||||
* Overlays UI should be moved here as well.
|
||||
*
|
||||
* @returns a DOM element for the legacy platform to render into.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class RenderingService {
|
||||
start({ chrome, targetDomElement }: StartDeps) {
|
||||
const chromeUi = chrome.getComponent();
|
||||
const legacyRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<div className="content" data-test-subj="kibanaChrome">
|
||||
{chromeUi}
|
||||
|
||||
<div ref={legacyRef} />
|
||||
</div>
|
||||
</I18nProvider>,
|
||||
targetDomElement
|
||||
);
|
||||
|
||||
return {
|
||||
legacyTargetDomElement: legacyRef.current!,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface RenderingStart {
|
||||
legacyTargetDomElement: HTMLDivElement;
|
||||
}
|
|
@ -1,91 +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 ngMock from 'ng_mock';
|
||||
import $ from 'jquery';
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { chromeNavControlsRegistry } from '../../registry/chrome_nav_controls';
|
||||
import { uiRegistry } from '../../registry/_registry';
|
||||
|
||||
describe('chrome nav controls', function () {
|
||||
let compile;
|
||||
let stubRegistry;
|
||||
|
||||
beforeEach(ngMock.module('kibana', function (PrivateProvider) {
|
||||
stubRegistry = uiRegistry({
|
||||
order: ['order']
|
||||
});
|
||||
|
||||
PrivateProvider.swap(chromeNavControlsRegistry, stubRegistry);
|
||||
}));
|
||||
|
||||
beforeEach(ngMock.inject(function ($compile, $rootScope) {
|
||||
compile = function () {
|
||||
const $el = $('<div kbn-chrome-append-nav-controls>');
|
||||
$rootScope.$apply();
|
||||
$compile($el)($rootScope);
|
||||
return $el;
|
||||
};
|
||||
}));
|
||||
|
||||
it('injects templates from the ui/registry/chrome_nav_controls registry', function () {
|
||||
stubRegistry.register(function () {
|
||||
return {
|
||||
name: 'control',
|
||||
order: 100,
|
||||
template: `<span id="testTemplateEl"></span>`
|
||||
};
|
||||
});
|
||||
|
||||
const $el = compile();
|
||||
expect($el.find('#testTemplateEl')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders controls in reverse order, assuming that each control will float:right', function () {
|
||||
stubRegistry.register(function () {
|
||||
return {
|
||||
name: 'control2',
|
||||
order: 2,
|
||||
template: `<span id="2", class="testControl"></span>`
|
||||
};
|
||||
});
|
||||
stubRegistry.register(function () {
|
||||
return {
|
||||
name: 'control1',
|
||||
order: 1,
|
||||
template: `<span id="1", class="testControl"></span>`
|
||||
};
|
||||
});
|
||||
stubRegistry.register(function () {
|
||||
return {
|
||||
name: 'control3',
|
||||
order: 3,
|
||||
template: `<span id="3", class="testControl"></span>`
|
||||
};
|
||||
});
|
||||
|
||||
const $el = compile();
|
||||
expect(
|
||||
$el.find('.testControl')
|
||||
.toArray()
|
||||
.map(el => el.id)
|
||||
).to.eql(['3', '2', '1']);
|
||||
});
|
||||
});
|
|
@ -2,7 +2,3 @@ $kbnGlobalNavClosedWidth: 53px;
|
|||
$kbnGlobalNavOpenWidth: 180px;
|
||||
$kbnGlobalNavLogoHeight: 70px;
|
||||
$kbnGlobalNavAppIconHeight: $euiSizeXXL + $euiSizeXS;
|
||||
|
||||
$kbnLoadingIndicatorBackgroundSize: $euiSizeXXL * 10;
|
||||
$kbnLoadingIndicatorColor1: tint($euiColorAccent, 15%);
|
||||
$kbnLoadingIndicatorColor2: tint($euiColorAccent, 60%);
|
||||
|
|
|
@ -47,6 +47,7 @@ import { initSavedObjectClient } from './api/saved_object_client';
|
|||
import { initChromeBasePathApi } from './api/base_path';
|
||||
import { initChromeInjectedVarsApi } from './api/injected_vars';
|
||||
import { initHelpExtensionApi } from './api/help_extension';
|
||||
import { npStart } from '../new_platform';
|
||||
|
||||
export const chrome = {};
|
||||
const internals = _.defaults(
|
||||
|
@ -80,6 +81,8 @@ initChromeControlsApi(chrome);
|
|||
templateApi(chrome, internals);
|
||||
initChromeThemeApi(chrome);
|
||||
|
||||
npStart.core.chrome.setAppTitle(chrome.getAppTitle());
|
||||
|
||||
const waitForBootstrap = new Promise(resolve => {
|
||||
chrome.bootstrap = function (targetDomElement) {
|
||||
// import chrome nav controls and hacks now so that they are executed after
|
||||
|
@ -92,8 +95,10 @@ const waitForBootstrap = new Promise(resolve => {
|
|||
document.body.setAttribute('id', `${internals.app.id}-app`);
|
||||
|
||||
chrome.setupAngular();
|
||||
targetDomElement.setAttribute('id', 'kibana-body');
|
||||
// targetDomElement.setAttribute('id', 'kibana-body');
|
||||
targetDomElement.setAttribute('kbn-chrome', 'true');
|
||||
targetDomElement.setAttribute('ng-class', '{ \'hidden-chrome\': !chrome.getVisible() }');
|
||||
targetDomElement.className = 'app-wrapper';
|
||||
angular.bootstrap(targetDomElement, ['kibana']);
|
||||
resolve(targetDomElement);
|
||||
};
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
@import './kbn_chrome';
|
||||
@import './loading_indicator';
|
||||
|
||||
@import './header_global_nav/index';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import './header_global_nav';
|
|
@ -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 { uiModules } from '../../../modules';
|
||||
import { Header } from './components/header';
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
import { chromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls';
|
||||
import { npStart } from '../../../new_platform';
|
||||
import { NavControlSide } from '.';
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('headerGlobalNav', (reactDirective, Private) => {
|
||||
const newPlatform = npStart.core;
|
||||
|
||||
// Continue to support legacy nav controls not registered with the NP.
|
||||
// NOTE: in future change this needs to be moved out of this directive.
|
||||
const navControls = Private(chromeHeaderNavControlsRegistry);
|
||||
(navControls.bySide[NavControlSide.Left] || [])
|
||||
.forEach(navControl => newPlatform.chrome.navControls.registerLeft({
|
||||
order: navControl.order,
|
||||
mount: navControl.render,
|
||||
}));
|
||||
(navControls.bySide[NavControlSide.Right] || [])
|
||||
.forEach(navControl => newPlatform.chrome.navControls.registerRight({
|
||||
order: navControl.order,
|
||||
mount: navControl.render,
|
||||
}));
|
||||
|
||||
return reactDirective(wrapInI18nContext(Header), [
|
||||
// scope accepted by directive, passed in as React props
|
||||
'appTitle',
|
||||
],
|
||||
{},
|
||||
// angular injected React props
|
||||
{
|
||||
isVisible$: newPlatform.chrome.getIsVisible$(),
|
||||
badge$: newPlatform.chrome.getBadge$(),
|
||||
breadcrumbs$: newPlatform.chrome.getBreadcrumbs$(),
|
||||
helpExtension$: newPlatform.chrome.getHelpExtension$(),
|
||||
navLinks$: newPlatform.chrome.navLinks.getNavLinks$(),
|
||||
forceAppSwitcherNavigation$: newPlatform.chrome.navLinks.getForceAppSwitcherNavigation$(),
|
||||
homeHref: newPlatform.http.basePath.prepend('/app/kibana#/home'),
|
||||
uiCapabilities: newPlatform.application.capabilities,
|
||||
recentlyAccessed$: newPlatform.chrome.recentlyAccessed.get$(),
|
||||
navControlsLeft$: newPlatform.chrome.navControls.getLeft$(),
|
||||
navControlsRight$: newPlatform.chrome.navControls.getRight$(),
|
||||
});
|
||||
});
|
|
@ -17,13 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './header_global_nav';
|
||||
|
||||
import { kbnChromeProvider } from './kbn_chrome';
|
||||
import { kbnAppendChromeNavControls } from './append_nav_controls';
|
||||
import './loading_indicator';
|
||||
|
||||
export function directivesProvider(chrome, internals) {
|
||||
kbnChromeProvider(chrome, internals);
|
||||
kbnAppendChromeNavControls(chrome, internals);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
<div class="content" chrome-context data-test-subj="kibanaChrome">
|
||||
<kbn-loading-indicator></kbn-loading-indicator>
|
||||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications
|
||||
list="notifList"
|
||||
></kbn-notifications>
|
||||
|
||||
<header-global-nav
|
||||
class="header-global-wrapper hide-for-sharing"
|
||||
app-title="chrome.getAppTitle()"
|
||||
data-test-subj="headerGlobalNav"
|
||||
></header-global-nav>
|
||||
<div id="globalBannerList"></div>
|
||||
|
||||
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
|
||||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications
|
||||
list="notifList"
|
||||
></kbn-notifications>
|
||||
|
||||
<div id="globalBannerList"></div>
|
||||
|
||||
<div
|
||||
class="application"
|
||||
ng-class="'tab-' + getFirstPathSegment() + ' ' + chrome.getApplicationClasses()"
|
||||
ng-view
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="application"
|
||||
ng-class="'tab-' + getFirstPathSegment() + ' ' + chrome.getApplicationClasses()"
|
||||
ng-view
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
@ -31,6 +31,8 @@ import {
|
|||
} from '../../notify';
|
||||
|
||||
import { I18nContext } from '../../i18n';
|
||||
import { npStart } from '../../new_platform';
|
||||
import { chromeHeaderNavControlsRegistry, NavControlSide } from '../../registry/chrome_header_nav_controls';
|
||||
|
||||
export function kbnChromeProvider(chrome, internals) {
|
||||
|
||||
|
@ -55,7 +57,7 @@ export function kbnChromeProvider(chrome, internals) {
|
|||
},
|
||||
|
||||
controllerAs: 'chrome',
|
||||
controller($scope, $location) {
|
||||
controller($scope, $location, Private) {
|
||||
// Notifications
|
||||
$scope.notifList = notify._notifs;
|
||||
|
||||
|
@ -63,6 +65,19 @@ export function kbnChromeProvider(chrome, internals) {
|
|||
return $location.path().split('/')[1];
|
||||
};
|
||||
|
||||
// Continue to support legacy nav controls not registered with the NP.
|
||||
const navControls = Private(chromeHeaderNavControlsRegistry);
|
||||
(navControls.bySide[NavControlSide.Left] || [])
|
||||
.forEach(navControl => npStart.core.chrome.navControls.registerLeft({
|
||||
order: navControl.order,
|
||||
mount: navControl.render,
|
||||
}));
|
||||
(navControls.bySide[NavControlSide.Right] || [])
|
||||
.forEach(navControl => npStart.core.chrome.navControls.registerRight({
|
||||
order: navControl.order,
|
||||
mount: navControl.render,
|
||||
}));
|
||||
|
||||
// Non-scope based code (e.g., React)
|
||||
|
||||
// Banners
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { NavControl } from '../chrome/directives/header_global_nav';
|
||||
import { IndexedArray } from '../indexed_array';
|
||||
import { uiRegistry, UIRegistry } from './_registry';
|
||||
|
||||
|
@ -25,6 +24,18 @@ interface ChromeHeaderNavControlsRegistryAccessors {
|
|||
bySide: { [typeName: string]: IndexedArray<NavControl> };
|
||||
}
|
||||
|
||||
export enum NavControlSide {
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
}
|
||||
|
||||
export interface NavControl {
|
||||
name: string;
|
||||
order: number;
|
||||
side: NavControlSide;
|
||||
render: (targetDomElement: HTMLDivElement) => (() => void);
|
||||
}
|
||||
|
||||
export type ChromeHeaderNavControlsRegistry = UIRegistry<NavControl> &
|
||||
ChromeHeaderNavControlsRegistryAccessors;
|
||||
|
||||
|
|
|
@ -35,7 +35,16 @@ export const UI_EXPORT_DEFAULTS = {
|
|||
'moment-timezone$': resolve(ROOT, 'webpackShims/moment-timezone')
|
||||
},
|
||||
|
||||
styleSheetPaths: [],
|
||||
styleSheetPaths:
|
||||
['light', 'dark'].map(theme => ({
|
||||
theme,
|
||||
localPath: resolve(ROOT, 'src/core/public/index.scss'),
|
||||
publicPath: `core.${theme}.css`,
|
||||
urlImports: {
|
||||
urlBase: 'built_assets/css/',
|
||||
publicDir: resolve(ROOT, 'src/core/public'),
|
||||
}
|
||||
})),
|
||||
|
||||
appExtensions: {
|
||||
fieldFormatEditors: [
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<div ng-controller="securityNavController" ng-show="!!user">
|
||||
<global-nav-link
|
||||
kbn-route="route"
|
||||
icon="'plugins/security/images/person.svg'"
|
||||
eui-icon-type="'user'"
|
||||
tooltip-content="accountTooltip(user.full_name || user.username)"
|
||||
label="user.full_name || user.username"
|
||||
data-test-subj="loggedInUser"
|
||||
></global-nav-link>
|
||||
|
||||
<global-nav-link
|
||||
kbn-route="'/logout'"
|
||||
icon="'plugins/security/images/logout.svg'"
|
||||
eui-icon-type="'exit'"
|
||||
tooltip-content="formatTooltip(logoutLabel)"
|
||||
label="logoutLabel"
|
||||
></global-nav-link>
|
||||
</div>
|
|
@ -5,54 +5,18 @@
|
|||
*/
|
||||
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { constant } from 'lodash';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { chromeNavControlsRegistry } from 'ui/registry/chrome_nav_controls';
|
||||
import template from 'plugins/security/views/nav_control/nav_control.html';
|
||||
import 'plugins/security/services/shield_user';
|
||||
import '../account/account';
|
||||
import { Path } from 'plugins/xpack_main/services/path';
|
||||
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
|
||||
|
||||
import { chromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls';
|
||||
import { chromeHeaderNavControlsRegistry, NavControlSide } from 'ui/registry/chrome_header_nav_controls';
|
||||
import { SecurityNavControl } from './nav_control_component';
|
||||
import { NavControlSide } from 'ui/chrome/directives/header_global_nav';
|
||||
|
||||
chromeNavControlsRegistry.register(constant({
|
||||
name: 'security',
|
||||
order: 1000,
|
||||
template
|
||||
}));
|
||||
|
||||
const module = uiModules.get('security', ['kibana']);
|
||||
module.controller('securityNavController', ($scope, ShieldUser, globalNavState, kbnBaseUrl, Private) => {
|
||||
const xpackInfo = Private(XPackInfoProvider);
|
||||
const showSecurityLinks = xpackInfo.get('features.security.showLinks');
|
||||
if (Path.isUnauthenticated() || !showSecurityLinks) return;
|
||||
|
||||
$scope.user = ShieldUser.getCurrent();
|
||||
$scope.route = `${kbnBaseUrl}#/account`;
|
||||
|
||||
$scope.accountTooltip = (tooltip) => {
|
||||
// If the sidebar is open and there's no disabled message,
|
||||
// then we don't need to show the tooltip.
|
||||
if (globalNavState.isOpen()) {
|
||||
return;
|
||||
}
|
||||
return tooltip;
|
||||
};
|
||||
|
||||
$scope.logoutLabel = i18n.translate('xpack.security.navControl.logoutLabel', {
|
||||
defaultMessage: 'Logout'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
chromeHeaderNavControlsRegistry.register((ShieldUser, kbnBaseUrl, Private) => ({
|
||||
name: 'security',
|
||||
|
|
|
@ -4,7 +4,7 @@ exports[`NavControlPopover renders without crashing 1`] = `
|
|||
<EuiPopover
|
||||
anchorPosition="downRight"
|
||||
button={
|
||||
<SpacesGlobalNavButton
|
||||
<SpacesHeaderNavButton
|
||||
linkIcon={
|
||||
<SpaceAvatar
|
||||
announceSpaceName={true}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { ButtonProps } from '../types';
|
||||
|
||||
export class SpacesGlobalNavButton extends Component<ButtonProps> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="kbnGlobalNavLink">
|
||||
<button className="kbnGlobalNavLink__anchor" onClick={this.props.toggleSpaceSelector}>
|
||||
<span className="kbnGlobalNavLink__icon"> {this.props.linkIcon} </span>
|
||||
<span className="kbnGlobalNavLink__title"> {this.props.linkTitle} </span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
<div ng-controller="spacesNavController">
|
||||
<div id="spacesNavReactRoot" />
|
||||
</div>
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { constant } from 'lodash';
|
||||
import { SpacesManager } from 'plugins/spaces/lib/spaces_manager';
|
||||
// @ts-ignore
|
||||
import template from 'plugins/spaces/views/nav_control/nav_control.html';
|
||||
|
@ -12,27 +11,18 @@ import { NavControlPopover } from 'plugins/spaces/views/nav_control/nav_control_
|
|||
// @ts-ignore
|
||||
import { Path } from 'plugins/xpack_main/services/path';
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { NavControlSide } from 'ui/chrome/directives/header_global_nav';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { chromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls';
|
||||
import {
|
||||
chromeHeaderNavControlsRegistry,
|
||||
NavControlSide,
|
||||
} from 'ui/registry/chrome_header_nav_controls';
|
||||
// @ts-ignore
|
||||
import { chromeNavControlsRegistry } from 'ui/registry/chrome_nav_controls';
|
||||
import { Space } from '../../../common/model/space';
|
||||
import { SpacesGlobalNavButton } from './components/spaces_global_nav_button';
|
||||
import { SpacesHeaderNavButton } from './components/spaces_header_nav_button';
|
||||
|
||||
chromeNavControlsRegistry.register(
|
||||
constant({
|
||||
name: 'spaces',
|
||||
order: 90,
|
||||
template,
|
||||
})
|
||||
);
|
||||
|
||||
const module = uiModules.get('spaces_nav', ['kibana']);
|
||||
|
||||
export interface SpacesNavState {
|
||||
|
@ -42,40 +32,6 @@ export interface SpacesNavState {
|
|||
|
||||
let spacesManager: SpacesManager;
|
||||
|
||||
module.controller('spacesNavController', ($scope: any, chrome: any, activeSpace: any) => {
|
||||
const domNode = document.getElementById(`spacesNavReactRoot`);
|
||||
const spaceSelectorURL = chrome.getInjected('spaceSelectorURL');
|
||||
|
||||
spacesManager = new SpacesManager(spaceSelectorURL);
|
||||
|
||||
let mounted = false;
|
||||
|
||||
$scope.$parent.$watch('isVisible', function isVisibleWatcher(isVisible: boolean) {
|
||||
if (isVisible && !mounted && !Path.isUnauthenticated()) {
|
||||
render(
|
||||
<I18nContext>
|
||||
<NavControlPopover
|
||||
spacesManager={spacesManager}
|
||||
activeSpace={activeSpace}
|
||||
anchorPosition={'rightCenter'}
|
||||
buttonClass={SpacesGlobalNavButton}
|
||||
/>
|
||||
</I18nContext>,
|
||||
domNode
|
||||
);
|
||||
mounted = true;
|
||||
}
|
||||
});
|
||||
|
||||
// unmount react on controller destroy
|
||||
$scope.$on('$destroy', () => {
|
||||
if (domNode) {
|
||||
unmountComponentAtNode(domNode);
|
||||
}
|
||||
mounted = false;
|
||||
});
|
||||
});
|
||||
|
||||
module.service('spacesNavState', (activeSpace: any) => {
|
||||
return {
|
||||
getActiveSpace: () => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { SpaceAvatar } from '../../components';
|
||||
import { spacesManagerMock } from '../../lib/mocks';
|
||||
import { SpacesGlobalNavButton } from './components/spaces_global_nav_button';
|
||||
import { SpacesHeaderNavButton } from './components/spaces_header_nav_button';
|
||||
import { NavControlPopover } from './nav_control_popover';
|
||||
|
||||
describe('NavControlPopover', () => {
|
||||
|
@ -25,7 +25,7 @@ describe('NavControlPopover', () => {
|
|||
activeSpace={activeSpace}
|
||||
spacesManager={spacesManager}
|
||||
anchorPosition={'downRight'}
|
||||
buttonClass={SpacesGlobalNavButton}
|
||||
buttonClass={SpacesHeaderNavButton}
|
||||
/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
@ -56,7 +56,7 @@ describe('NavControlPopover', () => {
|
|||
activeSpace={activeSpace}
|
||||
spacesManager={spacesManager}
|
||||
anchorPosition={'rightCenter'}
|
||||
buttonClass={SpacesGlobalNavButton}
|
||||
buttonClass={SpacesHeaderNavButton}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -235,14 +235,6 @@
|
|||
"common.ui.chrome.bigUrlWarningNotificationMessage": "{advancedSettingsLink} の {storeInSessionStorageParam} オプションを有効にするか、画面上のビジュアルをシンプルにしてください。",
|
||||
"common.ui.chrome.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定",
|
||||
"common.ui.chrome.bigUrlWarningNotificationTitle": "URL が大きく、Kibana の動作が停止する可能性があります",
|
||||
"common.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "ホームページに移動",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "ヘルプメニュー",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation": "ドキュメンテーションに移動",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuHelpDescription": "ドキュメンテーションでアップデートや情報、答えが得られます。",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuTitle": "ヘルプ",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}",
|
||||
"common.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近のアイテム",
|
||||
"common.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近閲覧",
|
||||
"common.ui.courier.fetch.failedToClearRequestErrorMessage": "返答から未完全または重複のリクエストを消去できませんでした。",
|
||||
"common.ui.courier.fetch.requestTimedOutNotificationMessage": "リクエストがタイムアウトしたため、データが不完全な可能性があります",
|
||||
"common.ui.courier.fetch.requestWasAbortedTwiceErrorMessage": "リクエストが 2 度中断されましたか?",
|
||||
|
@ -504,7 +496,6 @@
|
|||
"common.ui.paginateSelectableList.sortByButtonLabeDescendingScreenReaderOnly": "降順",
|
||||
"common.ui.paginateSelectableList.sortByButtonLabel": "名前",
|
||||
"common.ui.paginateSelectableList.sortByButtonLabelScreenReaderOnly": "並べ替え基準",
|
||||
"common.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel}、タイプ: {pageType}",
|
||||
"common.ui.savedObjectFinder.addNewItemButtonLabel": "新規 {item} を追加",
|
||||
"common.ui.savedObjectFinder.manageItemsButtonLabel": "{items} の管理",
|
||||
"common.ui.savedObjectFinder.noMatchesFoundDescription": "一致する {items} が見つかりません。",
|
||||
|
@ -632,6 +623,15 @@
|
|||
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした",
|
||||
"common.ui.welcomeErrorMessage": "Kibana が正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。",
|
||||
"common.ui.welcomeMessage": "Kibana を読み込み中",
|
||||
"core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "ホームページに移動",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "ヘルプメニュー",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation": "ドキュメンテーションに移動",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuHelpDescription": "ドキュメンテーションでアップデートや情報、答えが得られます。",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuTitle": "ヘルプ",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}",
|
||||
"core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近のアイテム",
|
||||
"core.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近閲覧",
|
||||
"core.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel}、タイプ: {pageType}",
|
||||
"data.filter.applyFilters.popupHeader": "適用するフィルターの選択",
|
||||
"data.filter.applyFiltersPopup.cancelButtonLabel": "キャンセル",
|
||||
"data.filter.applyFiltersPopup.saveButtonLabel": "適用",
|
||||
|
@ -8789,7 +8789,6 @@
|
|||
"xpack.security.management.users.userNameColumnName": "ユーザー名",
|
||||
"xpack.security.management.users.usersTitle": "ユーザー",
|
||||
"xpack.security.management.usersTitle": "ユーザー",
|
||||
"xpack.security.navControl.logoutLabel": "ログアウト",
|
||||
"xpack.security.navControlComponent.accountMenuAriaLabel": "アカウントメニュー",
|
||||
"xpack.security.navControlComponent.editProfileLinkText": "プロフィールを編集",
|
||||
"xpack.security.navControlComponent.logoutLinkText": "ログアウト",
|
||||
|
|
|
@ -234,14 +234,6 @@
|
|||
"common.ui.chrome.bigUrlWarningNotificationMessage": "在“{advancedSettingsLink}”启用“{storeInSessionStorageParam}”选项,或简化屏幕视觉效果。",
|
||||
"common.ui.chrome.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置",
|
||||
"common.ui.chrome.bigUrlWarningNotificationTitle": "URL 过大,Kibana 可能无法工作",
|
||||
"common.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "前往主页",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "帮助菜单",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation": "前往文档",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuHelpDescription": "在我们的文档中获取更新、信息以及答案。",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuTitle": "帮助",
|
||||
"common.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}",
|
||||
"common.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近项",
|
||||
"common.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近查看",
|
||||
"common.ui.courier.fetch.failedToClearRequestErrorMessage": "无法从响应中清除不完整或重复的请求。",
|
||||
"common.ui.courier.fetch.requestTimedOutNotificationMessage": "由于您的请求超时,因此数据可能不完整",
|
||||
"common.ui.courier.fetch.requestWasAbortedTwiceErrorMessage": "请求已中止两次?",
|
||||
|
@ -503,7 +495,6 @@
|
|||
"common.ui.paginateSelectableList.sortByButtonLabeDescendingScreenReaderOnly": "降序",
|
||||
"common.ui.paginateSelectableList.sortByButtonLabel": "名称",
|
||||
"common.ui.paginateSelectableList.sortByButtonLabelScreenReaderOnly": "排序依据",
|
||||
"common.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel},类型:{pageType}",
|
||||
"common.ui.savedObjectFinder.addNewItemButtonLabel": "添加新的 {item}",
|
||||
"common.ui.savedObjectFinder.manageItemsButtonLabel": "管理 {items}",
|
||||
"common.ui.savedObjectFinder.noMatchesFoundDescription": "未找到任何匹配的 {items}。",
|
||||
|
@ -631,6 +622,15 @@
|
|||
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "无法获取边界",
|
||||
"common.ui.welcomeErrorMessage": "Kibana 未正确加载。检查服务器输出以了解详情。",
|
||||
"common.ui.welcomeMessage": "正在加载 Kibana",
|
||||
"core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "前往主页",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "帮助菜单",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation": "前往文档",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuHelpDescription": "在我们的文档中获取更新、信息以及答案。",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuTitle": "帮助",
|
||||
"core.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}",
|
||||
"core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近项",
|
||||
"core.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近查看",
|
||||
"core.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel},类型:{pageType}",
|
||||
"data.filter.applyFilters.popupHeader": "选择要应用的筛选",
|
||||
"data.filter.applyFiltersPopup.cancelButtonLabel": "取消",
|
||||
"data.filter.applyFiltersPopup.saveButtonLabel": "应用",
|
||||
|
@ -8791,7 +8791,6 @@
|
|||
"xpack.security.management.users.userNameColumnName": "用户名",
|
||||
"xpack.security.management.users.usersTitle": "用户",
|
||||
"xpack.security.management.usersTitle": "用户",
|
||||
"xpack.security.navControl.logoutLabel": "注销",
|
||||
"xpack.security.navControlComponent.accountMenuAriaLabel": "帐户菜单",
|
||||
"xpack.security.navControlComponent.editProfileLinkText": "编辑配置文件",
|
||||
"xpack.security.navControlComponent.logoutLinkText": "注销",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue