Beats Management translations (#25228) (#26398)

* Beats Management translations

* Fix merge issues

* Revert translations for config

* Fix tslint error

* Add map for config translations

* Use Map

* Fix tslint

* Update i18n ids

* Fix tslint

* Remove commented code

* Revert translation of Filebeat and Metricbeat because they should be translated

* Update message id
This commit is contained in:
Maryia Lapata 2018-11-29 13:02:12 +03:00 committed by GitHub
parent 555d6281e4
commit 280e65229d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1295 additions and 273 deletions

View file

@ -12,13 +12,11 @@
"tableVis": "src/core_plugins/table_vis", "tableVis": "src/core_plugins/table_vis",
"regionMap": "src/core_plugins/region_map", "regionMap": "src/core_plugins/region_map",
"statusPage": "src/core_plugins/status_page", "statusPage": "src/core_plugins/status_page",
"tagCloud": "src/core_plugins/tagcloud",
"tagCloud": "src/core_plugins/tagcloud",
"tileMap": "src/core_plugins/tile_map", "tileMap": "src/core_plugins/tile_map",
"timelion": "src/core_plugins/timelion", "timelion": "src/core_plugins/timelion",
"tsvb": "src/core_plugins/metrics",
"tagCloud": "src/core_plugins/tagcloud", "tagCloud": "src/core_plugins/tagcloud",
"tsvb": "src/core_plugins/metrics", "tsvb": "src/core_plugins/metrics",
"xpack.beatsManagement": "x-pack/plugins/beats_management",
"xpack.graph": "x-pack/plugins/graph", "xpack.graph": "x-pack/plugins/graph",
"xpack.grokDebugger": "x-pack/plugins/grokdebugger", "xpack.grokDebugger": "x-pack/plugins/grokdebugger",
"xpack.idxMgmt": "x-pack/plugins/index_management", "xpack.idxMgmt": "x-pack/plugins/index_management",

View file

@ -6,25 +6,30 @@
// @ts-ignore // @ts-ignore
import { EuiBasicTable, EuiLink } from '@elastic/eui'; import { EuiBasicTable, EuiLink } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { ConfigurationBlock } from '../../common/domain_types'; import { ConfigurationBlock } from '../../common/domain_types';
import { supportedConfigs } from '../config_schemas'; import { getSupportedConfig } from '../config_schemas_translations_map';
interface ComponentProps { interface ComponentProps {
configs: ConfigurationBlock[]; configs: ConfigurationBlock[];
onConfigClick: (action: 'edit' | 'delete', config: ConfigurationBlock) => any; onConfigClick: (action: 'edit' | 'delete', config: ConfigurationBlock) => any;
intl: InjectedIntl;
} }
export const ConfigList: React.SFC<ComponentProps> = props => ( const ConfigListUi: React.SFC<ComponentProps> = props => (
<EuiBasicTable <EuiBasicTable
items={props.configs || []} items={props.configs || []}
columns={[ columns={[
{ {
field: 'type', field: 'type',
name: 'Type', name: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.typeColumnName',
defaultMessage: 'Type',
}),
truncateText: false, truncateText: false,
render: (value: string, config: ConfigurationBlock) => { render: (value: string, config: ConfigurationBlock) => {
const type = supportedConfigs.find((sc: any) => sc.value === config.type); const type = getSupportedConfig().find((sc: any) => sc.value === config.type);
return ( return (
<EuiLink onClick={() => props.onConfigClick('edit', config)}> <EuiLink onClick={() => props.onConfigClick('edit', config)}>
@ -35,22 +40,43 @@ export const ConfigList: React.SFC<ComponentProps> = props => (
}, },
{ {
field: 'module', field: 'module',
name: 'Module', name: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.moduleColumnName',
defaultMessage: 'Module',
}),
truncateText: false, truncateText: false,
render: (value: string) => { render: (value: string) => {
return value || 'N/A'; return (
value ||
props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.moduleColumn.notAvailibaleLabel',
defaultMessage: 'N/A',
})
);
}, },
}, },
{ {
field: 'description', field: 'description',
name: 'Description', name: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.descriptionColumnName',
defaultMessage: 'Description',
}),
}, },
{ {
name: 'Actions', name: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.actionsColumnName',
defaultMessage: 'Actions',
}),
actions: [ actions: [
{ {
name: 'Remove', name: props.intl.formatMessage({
description: 'Remove this config from tag', id: 'xpack.beatsManagement.tagTable.actions.removeButtonAriaLabel',
defaultMessage: 'Remove',
}),
description: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.actions.removeTooltip',
defaultMessage: 'Remove this config from tag',
}),
type: 'icon', type: 'icon',
icon: 'trash', icon: 'trash',
onClick: (item: ConfigurationBlock) => props.onConfigClick('delete', item), onClick: (item: ConfigurationBlock) => props.onConfigClick('delete', item),
@ -60,3 +86,5 @@ export const ConfigList: React.SFC<ComponentProps> = props => (
]} ]}
/> />
); );
export const ConfigList = injectI18n(ConfigListUi);

View file

@ -5,20 +5,22 @@
*/ */
import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { ActionDefinition } from './table_type_configs'; import { ActionDefinition } from './table_type_configs';
interface ActionButtonProps { interface ActionButtonProps {
itemName: 'Beats' | 'Tags'; itemName: 'Beats' | 'Tags';
actions: ActionDefinition[]; actions: ActionDefinition[];
intl: InjectedIntl;
isPopoverVisible: boolean; isPopoverVisible: boolean;
actionHandler(action: string, payload?: any): void; actionHandler(action: string, payload?: any): void;
hidePopover(): void; hidePopover(): void;
showPopover(): void; showPopover(): void;
} }
export function ActionButton(props: ActionButtonProps) { export const ActionButton = injectI18n((props: ActionButtonProps) => {
const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover } = props; const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover, intl } = props;
if (actions.length === 0) { if (actions.length === 0) {
return null; return null;
} }
@ -27,7 +29,10 @@ export function ActionButton(props: ActionButtonProps) {
anchorPosition="downLeft" anchorPosition="downLeft"
button={ button={
<EuiButton iconSide="right" iconType="arrowDown" onClick={showPopover}> <EuiButton iconSide="right" iconType="arrowDown" onClick={showPopover}>
Bulk Action <FormattedMessage
id="xpack.beatsManagement.table.bulkActionButtonLabel"
defaultMessage="Bulk Action"
/>
</EuiButton> </EuiButton>
} }
closePopover={hidePopover} closePopover={hidePopover}
@ -41,7 +46,13 @@ export function ActionButton(props: ActionButtonProps) {
panels={[ panels={[
{ {
id: 0, id: 0,
title: `Manage ${props.itemName}`, title: intl.formatMessage(
{
id: 'xpack.beatsManagement.table.bulkActionMenuLabel',
defaultMessage: 'Manage {itemName}',
},
{ itemName: props.itemName }
),
items: actions.map(action => ({ items: actions.map(action => ({
...action, ...action,
onClick: () => actionHandler(action.action), onClick: () => actionHandler(action.action),
@ -51,4 +62,4 @@ export function ActionButton(props: ActionButtonProps) {
/> />
</EuiPopover> </EuiPopover>
); );
} });

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { i18n } from '@kbn/i18n';
import { AssignmentActionType } from './table'; import { AssignmentActionType } from './table';
export interface AssignmentControlSchema { export interface AssignmentControlSchema {
@ -22,20 +23,32 @@ export interface AssignmentControlSchema {
export const beatsListAssignmentOptions: AssignmentControlSchema[] = [ export const beatsListAssignmentOptions: AssignmentControlSchema[] = [
{ {
grow: false, grow: false,
name: 'Unenroll selected', name: i18n.translate('xpack.beatsManagement.beatsListAssignmentOptions.unenrollButtonLabel', {
defaultMessage: 'Unenroll selected',
}),
showWarning: true, showWarning: true,
warningHeading: 'Unenroll selected beats?', warningHeading: i18n.translate(
warningMessage: 'The selected Beats will no longer use central management', 'xpack.beatsManagement.beatsListAssignmentOptions.unenrollBeatsWarninigTitle',
{ defaultMessage: 'Unenroll selected beats?' }
),
warningMessage: i18n.translate(
'xpack.beatsManagement.beatsListAssignmentOptions.unenrollBeatsWarninigMessage',
{ defaultMessage: 'The selected Beats will no longer use central management' }
),
action: AssignmentActionType.Delete, action: AssignmentActionType.Delete,
danger: true, danger: true,
}, },
{ {
name: 'Set tags', name: i18n.translate('xpack.beatsManagement.beatsListAssignmentOptions.setTagsButtonLabel', {
defaultMessage: 'Set tags',
}),
grow: false, grow: false,
lazyLoad: true, lazyLoad: true,
panel: { panel: {
id: 1, id: 1,
name: 'Assign tags', name: i18n.translate('xpack.beatsManagement.beatsListAssignmentOptions.assignTagsName', {
defaultMessage: 'Assign tags',
}),
}, },
}, },
]; ];
@ -44,10 +57,18 @@ export const tagListAssignmentOptions: AssignmentControlSchema[] = [
{ {
danger: true, danger: true,
grow: false, grow: false,
name: 'Remove tag(s)', name: i18n.translate('xpack.beatsManagement.tagListAssignmentOptions.removeTagsButtonLabel', {
defaultMessage: 'Remove tag(s)',
}),
showWarning: true, showWarning: true,
warningHeading: 'Remove tag(s)', warningHeading: i18n.translate(
warningMessage: 'Remove the tag?', 'xpack.beatsManagement.tagListAssignmentOptions.removeTagsWarninigTitle',
{ defaultMessage: 'Remove tag(s)' }
),
warningMessage: i18n.translate(
'xpack.beatsManagement.tagListAssignmentOptions.removeTagWarninigMessage',
{ defaultMessage: 'Remove the tag?' }
),
action: AssignmentActionType.Delete, action: AssignmentActionType.Delete,
}, },
]; ];
@ -56,10 +77,18 @@ export const tagConfigAssignmentOptions: AssignmentControlSchema[] = [
{ {
danger: true, danger: true,
grow: false, grow: false,
name: 'Remove tag(s)', name: i18n.translate('xpack.beatsManagement.tagConfigAssignmentOptions.removeTagsButtonLabel', {
defaultMessage: 'Remove tag(s)',
}),
showWarning: true, showWarning: true,
warningHeading: 'Remove tag(s)', warningHeading: i18n.translate(
warningMessage: 'Remove the tag from the selected beat(s)?', 'xpack.beatsManagement.tagConfigAssignmentOptions.removeTagsWarninigTitle',
{ defaultMessage: 'Remove tag(s)' }
),
warningMessage: i18n.translate(
'xpack.beatsManagement.tagConfigAssignmentOptions.removeTagsWarninigMessage',
{ defaultMessage: 'Remove the tag from the selected beat(s)?' }
),
action: AssignmentActionType.Delete, action: AssignmentActionType.Delete,
}, },
]; ];

View file

@ -5,6 +5,7 @@
*/ */
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { AutocompleteField } from '../autocomplete_field/index'; import { AutocompleteField } from '../autocomplete_field/index';
import { OptionControl } from '../table_controls'; import { OptionControl } from '../table_controls';
@ -15,13 +16,15 @@ interface ControlBarProps {
assignmentOptions: AssignmentOptionsType; assignmentOptions: AssignmentOptionsType;
kueryBarProps?: KueryBarProps; kueryBarProps?: KueryBarProps;
selectionCount: number; selectionCount: number;
intl: InjectedIntl;
} }
export function ControlBar(props: ControlBarProps) { function ControlBarUi(props: ControlBarProps) {
const { const {
assignmentOptions: { actionHandler, items, schema, type }, assignmentOptions: { actionHandler, items, schema, type },
kueryBarProps, kueryBarProps,
selectionCount, selectionCount,
intl,
} = props; } = props;
if (type === 'none') { if (type === 'none') {
@ -41,9 +44,17 @@ export function ControlBar(props: ControlBarProps) {
</EuiFlexItem> </EuiFlexItem>
{kueryBarProps && ( {kueryBarProps && (
<EuiFlexItem> <EuiFlexItem>
<AutocompleteField {...kueryBarProps} placeholder="Filter results" /> <AutocompleteField
{...kueryBarProps}
placeholder={intl.formatMessage({
id: 'xpack.beatsManagement.table.filterResultsPlaceholder',
defaultMessage: 'Filter results',
})}
/>
</EuiFlexItem> </EuiFlexItem>
)} )}
</EuiFlexGroup> </EuiFlexGroup>
); );
} }
export const ControlBar = injectI18n(ControlBarUi);

View file

@ -9,6 +9,7 @@ import {
EuiInMemoryTable, EuiInMemoryTable,
EuiSpacer, EuiSpacer,
} from '@elastic/eui'; } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; import { AutocompleteSuggestion } from 'ui/autocomplete_providers';
@ -92,7 +93,10 @@ export class Table extends React.Component<TableProps, TableState> {
: { : {
onSelectionChange: this.setSelection, onSelectionChange: this.setSelection,
selectable: () => true, selectable: () => true,
selectableMessage: () => 'Select this beat', selectableMessage: () =>
i18n.translate('xpack.beatsManagement.table.selectThisBeatTooltip', {
defaultMessage: 'Select this beat',
}),
selection: this.state.selection, selection: this.state.selection,
}; };

View file

@ -5,6 +5,7 @@
*/ */
import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiToolTip, IconColor } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiToolTip, IconColor } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { first, sortBy, sortByOrder, uniq } from 'lodash'; import { first, sortBy, sortByOrder, uniq } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import React from 'react'; import React from 'react';
@ -56,7 +57,9 @@ export const BeatsTableType: TableType = {
columnDefinitions: [ columnDefinitions: [
{ {
field: 'name', field: 'name',
name: 'Beat name', name: i18n.translate('xpack.beatsManagement.beatsTable.beatNameTitle', {
defaultMessage: 'Beat name',
}),
render: (name: string, beat: CMPopulatedBeat) => ( render: (name: string, beat: CMPopulatedBeat) => (
<ConnectedLink path={`/beat/${beat.id}`}>{name}</ConnectedLink> <ConnectedLink path={`/beat/${beat.id}`}>{name}</ConnectedLink>
), ),
@ -64,12 +67,16 @@ export const BeatsTableType: TableType = {
}, },
{ {
field: 'type', field: 'type',
name: 'Type', name: i18n.translate('xpack.beatsManagement.beatsTable.typeTitle', {
defaultMessage: 'Type',
}),
sortable: true, sortable: true,
}, },
{ {
field: 'full_tags', field: 'full_tags',
name: 'Tags', name: i18n.translate('xpack.beatsManagement.beatsTable.tagsTitle', {
defaultMessage: 'Tags',
}),
render: (value: string, beat: CMPopulatedBeat) => ( render: (value: string, beat: CMPopulatedBeat) => (
<EuiFlexGroup wrap responsive={true} gutterSize="xs"> <EuiFlexGroup wrap responsive={true} gutterSize="xs">
{(sortBy(beat.full_tags, 'id') || []).map(tag => ( {(sortBy(beat.full_tags, 'id') || []).map(tag => (
@ -86,26 +93,60 @@ export const BeatsTableType: TableType = {
{ {
// TODO: update to use actual metadata field // TODO: update to use actual metadata field
field: 'config_status', field: 'config_status',
name: 'Config Status', name: i18n.translate('xpack.beatsManagement.beatsTable.configStatusTitle', {
defaultMessage: 'Config Status',
}),
render: (value: string, beat: CMPopulatedBeat) => { render: (value: string, beat: CMPopulatedBeat) => {
let color: IconColor = 'success'; let color: IconColor = 'success';
let statusText = 'OK'; let statusText = i18n.translate('xpack.beatsManagement.beatsTable.configStatus.okLabel', {
let tooltipText = 'Beat successfully applied latest config'; defaultMessage: 'OK',
});
let tooltipText = i18n.translate(
'xpack.beatsManagement.beatsTable.configStatus.okTooltip',
{
defaultMessage: 'Beat successfully applied latest config',
}
);
switch (beat.config_status) { switch (beat.config_status) {
case 'UNKNOWN': case 'UNKNOWN':
color = 'subdued'; color = 'subdued';
statusText = 'Offline'; statusText = i18n.translate(
'xpack.beatsManagement.beatsTable.configStatus.offlineLabel',
{
defaultMessage: 'Offline',
}
);
if (moment().diff(beat.last_checkin, 'minutes') >= 10) { if (moment().diff(beat.last_checkin, 'minutes') >= 10) {
tooltipText = 'This Beat has not connected to kibana in over 10min'; tooltipText = i18n.translate(
'xpack.beatsManagement.beatsTable.configStatus.noConnectionTooltip',
{
defaultMessage: 'This Beat has not connected to kibana in over 10min',
}
);
} else { } else {
tooltipText = 'This Beat has not yet been started.'; tooltipText = i18n.translate(
'xpack.beatsManagement.beatsTable.configStatus.notStartedTooltip',
{
defaultMessage: 'This Beat has not yet been started.',
}
);
} }
break; break;
case 'ERROR': case 'ERROR':
color = 'danger'; color = 'danger';
statusText = 'Error'; statusText = i18n.translate(
tooltipText = 'Please check the logs of this Beat for error details'; 'xpack.beatsManagement.beatsTable.configStatus.errorLabel',
{
defaultMessage: 'Error',
}
);
tooltipText = i18n.translate(
'xpack.beatsManagement.beatsTable.configStatus.errorTooltip',
{
defaultMessage: 'Please check the logs of this Beat for error details',
}
);
break; break;
} }
@ -121,7 +162,9 @@ export const BeatsTableType: TableType = {
}, },
{ {
field: 'full_tags', field: 'full_tags',
name: 'Last config update', name: i18n.translate('xpack.beatsManagement.beatsTable.lastConfigUpdateTitle', {
defaultMessage: 'Last config update',
}),
render: (tags: BeatTag[]) => render: (tags: BeatTag[]) =>
tags.length ? ( tags.length ? (
<span> <span>
@ -134,7 +177,9 @@ export const BeatsTableType: TableType = {
controlDefinitions: (data: any[]) => ({ controlDefinitions: (data: any[]) => ({
actions: [ actions: [
{ {
name: 'Disenroll Selected', name: i18n.translate('xpack.beatsManagement.beatsTable.disenrollSelectedLabel', {
defaultMessage: 'Disenroll Selected',
}),
action: 'delete', action: 'delete',
danger: true, danger: true,
}, },
@ -143,7 +188,9 @@ export const BeatsTableType: TableType = {
{ {
type: 'field_value_selection', type: 'field_value_selection',
field: 'type', field: 'type',
name: 'Type', name: i18n.translate('xpack.beatsManagement.beatsTable.typeLabel', {
defaultMessage: 'Type',
}),
options: uniq(data.map(({ type }: { type: any }) => ({ value: type })), 'value'), options: uniq(data.map(({ type }: { type: any }) => ({ value: type })), 'value'),
}, },
], ],
@ -155,7 +202,9 @@ export const TagsTableType: TableType = {
columnDefinitions: [ columnDefinitions: [
{ {
field: 'id', field: 'id',
name: 'Tag name', name: i18n.translate('xpack.beatsManagement.tagsTable.tagNameTitle', {
defaultMessage: 'Tag name',
}),
render: (id: string, tag: BeatTag) => ( render: (id: string, tag: BeatTag) => (
<ConnectedLink path={`/tag/edit/${tag.id}`}> <ConnectedLink path={`/tag/edit/${tag.id}`}>
<TagBadge tag={tag} /> <TagBadge tag={tag} />
@ -167,7 +216,9 @@ export const TagsTableType: TableType = {
{ {
align: 'right', align: 'right',
field: 'configuration_blocks', field: 'configuration_blocks',
name: 'Configurations', name: i18n.translate('xpack.beatsManagement.tagsTable.configurationsTitle', {
defaultMessage: 'Configurations',
}),
render: (configurationBlocks: ConfigurationBlock[]) => ( render: (configurationBlocks: ConfigurationBlock[]) => (
<div>{configurationBlocks.length}</div> <div>{configurationBlocks.length}</div>
), ),
@ -176,7 +227,9 @@ export const TagsTableType: TableType = {
{ {
align: 'right', align: 'right',
field: 'last_updated', field: 'last_updated',
name: 'Last update', name: i18n.translate('xpack.beatsManagement.tagsTable.lastUpdateTitle', {
defaultMessage: 'Last update',
}),
render: (lastUpdate: Date) => <div>{moment(lastUpdate).fromNow()}</div>, render: (lastUpdate: Date) => <div>{moment(lastUpdate).fromNow()}</div>,
sortable: true, sortable: true,
}, },
@ -184,7 +237,9 @@ export const TagsTableType: TableType = {
controlDefinitions: (data: any) => ({ controlDefinitions: (data: any) => ({
actions: [ actions: [
{ {
name: 'Remove Selected', name: i18n.translate('xpack.beatsManagement.tagsTable.removeSelectedLabel', {
defaultMessage: 'Remove Selected',
}),
action: 'delete', action: 'delete',
danger: true, danger: true,
}, },
@ -198,7 +253,9 @@ export const BeatDetailTagsTable: TableType = {
columnDefinitions: [ columnDefinitions: [
{ {
field: 'id', field: 'id',
name: 'Tag name', name: i18n.translate('xpack.beatsManagement.beatTagsTable.tagNameTitle', {
defaultMessage: 'Tag name',
}),
render: (id: string, tag: BeatTag) => ( render: (id: string, tag: BeatTag) => (
<ConnectedLink path={`/tag/edit/${tag.id}`}> <ConnectedLink path={`/tag/edit/${tag.id}`}>
<TagBadge tag={tag} /> <TagBadge tag={tag} />
@ -210,14 +267,18 @@ export const BeatDetailTagsTable: TableType = {
{ {
align: 'right', align: 'right',
field: 'configuration_blocks', field: 'configuration_blocks',
name: 'Configurations', name: i18n.translate('xpack.beatsManagement.beatTagsTable.configurationsTitle', {
defaultMessage: 'Configurations',
}),
render: (configurations: ConfigurationBlock[]) => <span>{configurations.length}</span>, render: (configurations: ConfigurationBlock[]) => <span>{configurations.length}</span>,
sortable: true, sortable: true,
}, },
{ {
align: 'right', align: 'right',
field: 'last_updated', field: 'last_updated',
name: 'Last update', name: i18n.translate('xpack.beatsManagement.beatTagsTable.lastUpdateTitle', {
defaultMessage: 'Last update',
}),
render: (lastUpdate: string) => <span>{moment(lastUpdate).fromNow()}</span>, render: (lastUpdate: string) => <span>{moment(lastUpdate).fromNow()}</span>,
sortable: true, sortable: true,
}, },
@ -227,12 +288,16 @@ export const BeatDetailTagsTable: TableType = {
filters: [], filters: [],
primaryActions: [ primaryActions: [
{ {
name: 'Add Tag', name: i18n.translate('xpack.beatsManagement.beatTagsTable.addTagLabel', {
defaultMessage: 'Add Tag',
}),
action: 'add', action: 'add',
danger: false, danger: false,
}, },
{ {
name: 'Remove Selected', name: i18n.translate('xpack.beatsManagement.beatTagsTable.removeSelectedLabel', {
defaultMessage: 'Remove Selected',
}),
action: 'remove', action: 'remove',
danger: true, danger: true,
}, },

View file

@ -10,6 +10,7 @@ import {
EuiOverlayMask, EuiOverlayMask,
EuiTextColor, EuiTextColor,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { AssignmentActionType } from '../table'; import { AssignmentActionType } from '../table';
@ -60,14 +61,33 @@ export class ActionControl extends React.PureComponent<ActionControlProps, Actio
<EuiOverlayMask> <EuiOverlayMask>
<EuiConfirmModal <EuiConfirmModal
buttonColor={danger ? 'danger' : 'primary'} buttonColor={danger ? 'danger' : 'primary'}
cancelButtonText="Cancel" cancelButtonText={
confirmButtonText="Confirm" <FormattedMessage
id="xpack.beatsManagement.confirmModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
}
confirmButtonText={
<FormattedMessage
id="xpack.beatsManagement.confirmModal.confirmButtonLabel"
defaultMessage="Confirm"
/>
}
onConfirm={() => { onConfirm={() => {
actionHandler(action); actionHandler(action);
this.setState({ showModal: false }); this.setState({ showModal: false });
}} }}
onCancel={() => this.setState({ showModal: false })} onCancel={() => this.setState({ showModal: false })}
title={warningHeading ? warningHeading : 'Confirm'} title={
warningHeading ? (
warningHeading
) : (
<FormattedMessage
id="xpack.beatsManagement.confirmModal.confirmWarningTitle"
defaultMessage="Confirm"
/>
)
}
> >
{warningMessage} {warningMessage}
</EuiConfirmModal> </EuiConfirmModal>

View file

@ -16,6 +16,7 @@ import {
EuiToolTipProps, EuiToolTipProps,
} from '@elastic/eui'; } from '@elastic/eui';
import { EuiIcon } from '@elastic/eui'; import { EuiIcon } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { isArray } from 'lodash'; import { isArray } from 'lodash';
import React from 'react'; import React from 'react';
import { AssignmentControlSchema } from '../table'; import { AssignmentControlSchema } from '../table';
@ -24,6 +25,7 @@ import { ActionControl } from './action_control';
import { TagBadgeList } from './tag_badge_list'; import { TagBadgeList } from './tag_badge_list';
interface ComponentProps { interface ComponentProps {
intl: InjectedIntl;
itemType: string; itemType: string;
items?: any[]; items?: any[];
schema: AssignmentControlSchema[]; schema: AssignmentControlSchema[];
@ -40,7 +42,7 @@ interface FixedEuiToolTipProps extends EuiToolTipProps {
} }
const FixedEuiToolTip = (EuiToolTip as any) as React.SFC<FixedEuiToolTipProps>; const FixedEuiToolTip = (EuiToolTip as any) as React.SFC<FixedEuiToolTipProps>;
export class OptionControl extends React.PureComponent<ComponentProps, ComponentState> { class OptionControlUi extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) { constructor(props: ComponentProps) {
super(props); super(props);
@ -53,7 +55,7 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
schemaOrArray: AssignmentControlSchema | AssignmentControlSchema[], schemaOrArray: AssignmentControlSchema | AssignmentControlSchema[],
panels: any = [] panels: any = []
) { ) {
const { items, actionHandler } = this.props; const { items, actionHandler, intl } = this.props;
let schema: AssignmentControlSchema | null = null; let schema: AssignmentControlSchema | null = null;
let schemaArray: AssignmentControlSchema[] | null = null; let schemaArray: AssignmentControlSchema[] | null = null;
@ -91,14 +93,23 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
}); });
} else { } else {
if (items === undefined) { if (items === undefined) {
panel.content = 'Unknown Error.'; panel.content = intl.formatMessage({
id: 'xpack.beatsManagement.tableControls.unknownErrorMessage',
defaultMessage: 'Unknown Error.',
});
} else if (items.length === 0) { } else if (items.length === 0) {
panel.content = ( panel.content = (
<EuiPanel> <EuiPanel>
<EuiCard <EuiCard
icon={<EuiIcon size="l" type="bolt" />} icon={<EuiIcon size="l" type="bolt" />}
title="No tags found." title={intl.formatMessage({
description="Please create a new configuration tag." id: 'xpack.beatsManagement.tableControls.noTagsFoundTitle',
defaultMessage: 'No tags found.',
})}
description={intl.formatMessage({
id: 'xpack.beatsManagement.tableControls.noTagsFoundDescription',
defaultMessage: 'Please create a new configuration tag.',
})}
/> />
</EuiPanel> </EuiPanel>
); );
@ -121,7 +132,7 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
} }
public render() { public render() {
const { itemType, selectionCount, schema } = this.props; const { itemType, selectionCount, schema, intl } = this.props;
return ( return (
<EuiPopover <EuiPopover
@ -131,8 +142,21 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
delay="long" delay="long"
content={ content={
selectionCount === 0 selectionCount === 0
? `Select ${itemType} to perform operations such as setting tags and unenrolling Beats.` ? intl.formatMessage(
: `Manage your selected ${itemType}` {
id: 'xpack.beatsManagement.tableControls.selectItemDescription',
defaultMessage:
'Select {itemType} to perform operations such as setting tags and unenrolling Beats.',
},
{ itemType }
)
: intl.formatMessage(
{
id: 'xpack.beatsManagement.tableControls.manageSelectedItemDescription',
defaultMessage: 'Manage your selected {itemType}',
},
{ itemType }
)
} }
> >
<EuiButton <EuiButton
@ -146,7 +170,11 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
}); });
}} }}
> >
Manage {itemType} <FormattedMessage
id="xpack.beatsManagement.tableControls.manageItemButtonLabel"
defaultMessage="Manage {itemType}"
values={{ itemType }}
/>
</EuiButton> </EuiButton>
</FixedEuiToolTip> </FixedEuiToolTip>
} }
@ -164,3 +192,5 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
); );
} }
} }
export const OptionControl = injectI18n(OptionControlUi);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
// @ts-ignore // @ts-ignore
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import Formsy, { addValidationRule, FieldValue, FormData } from 'formsy-react'; import Formsy, { addValidationRule, FieldValue, FormData } from 'formsy-react';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { get } from 'lodash'; import { get } from 'lodash';
@ -55,6 +56,7 @@ addValidationRule('isYaml', (values: FormData, value: FieldValue) => {
}); });
interface ComponentProps { interface ComponentProps {
intl: InjectedIntl;
values: ConfigurationBlock; values: ConfigurationBlock;
schema: YamlConfigSchema[]; schema: YamlConfigSchema[];
id: string; id: string;
@ -62,7 +64,7 @@ interface ComponentProps {
canSubmit(canIt: boolean): any; canSubmit(canIt: boolean): any;
} }
export class ConfigForm extends React.Component<ComponentProps, any> { class ConfigFormUi extends React.Component<ComponentProps, any> {
private form = React.createRef<HTMLButtonElement>(); private form = React.createRef<HTMLButtonElement>();
constructor(props: ComponentProps) { constructor(props: ComponentProps) {
super(props); super(props);
@ -97,6 +99,7 @@ export class ConfigForm extends React.Component<ComponentProps, any> {
this.props.onSubmit(model); this.props.onSubmit(model);
}; };
public render() { public render() {
const { intl } = this.props;
return ( return (
<div> <div>
<br /> <br />
@ -181,9 +184,15 @@ export class ConfigForm extends React.Component<ComponentProps, any> {
)} )}
helpText={schema.ui.helpText} helpText={schema.ui.helpText}
label={schema.ui.label} label={schema.ui.label}
options={[{ value: '', text: 'Please Select An Option' }].concat( options={[
schema.options || [] {
)} value: '',
text: intl.formatMessage({
id: 'xpack.beatsManagement.tagConfig.selectOptionLabel',
defaultMessage: 'Please Select An Option',
}),
},
].concat(schema.options || [])}
validations={schema.validations} validations={schema.validations}
validationError={schema.error} validationError={schema.error}
required={schema.required} required={schema.required}
@ -225,3 +234,4 @@ export class ConfigForm extends React.Component<ComponentProps, any> {
); );
} }
} }
export const ConfigForm = injectI18n(ConfigFormUi, { withRef: true });

View file

@ -27,20 +27,23 @@ import {
EuiTabbedContent, EuiTabbedContent,
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { ConfigurationBlock } from '../../../../common/domain_types'; import { ConfigurationBlock } from '../../../../common/domain_types';
import { supportedConfigs } from '../../../config_schemas'; import { getSupportedConfig } from '../../../config_schemas_translations_map';
import { ConfigForm } from './config_form'; import { ConfigForm } from './config_form';
interface ComponentProps { interface ComponentProps {
intl: InjectedIntl;
configBlock?: ConfigurationBlock; configBlock?: ConfigurationBlock;
onClose(): any; onClose(): any;
onSave?(config: ConfigurationBlock): any; onSave?(config: ConfigurationBlock): any;
} }
export class ConfigView extends React.Component<ComponentProps, any> { class ConfigViewUi extends React.Component<ComponentProps, any> {
private form = React.createRef<any>(); private form = React.createRef<any>();
private editMode: boolean; private editMode: boolean;
private supportedConfigs = getSupportedConfig();
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.editMode = props.configBlock !== undefined; this.editMode = props.configBlock !== undefined;
@ -48,7 +51,7 @@ export class ConfigView extends React.Component<ComponentProps, any> {
this.state = { this.state = {
valid: false, valid: false,
configBlock: props.configBlock || { configBlock: props.configBlock || {
type: supportedConfigs[0].value, type: this.supportedConfigs[0].value,
}, },
}; };
} }
@ -62,42 +65,77 @@ export class ConfigView extends React.Component<ComponentProps, any> {
})); }));
}; };
public render() { public render() {
const { intl } = this.props;
return ( return (
<EuiFlyout onClose={this.props.onClose}> <EuiFlyout onClose={this.props.onClose}>
<EuiFlyoutHeader> <EuiFlyoutHeader>
<EuiTitle size="m"> <EuiTitle size="m">
<h2> <h2>
{this.editMode {this.editMode ? (
? this.props.onSave this.props.onSave ? (
? 'Edit configuration block' <FormattedMessage
: 'View configuration block' id="xpack.beatsManagement.tagConfig.editConfigurationTitle"
: 'Add configuration block'} defaultMessage="Edit configuration block"
/>
) : (
<FormattedMessage
id="xpack.beatsManagement.tagConfig.viewConfigurationTitle"
defaultMessage="View configuration block"
/>
)
) : (
<FormattedMessage
id="xpack.beatsManagement.tagConfig.addConfigurationTitle"
defaultMessage="Add configuration block"
/>
)}
</h2> </h2>
</EuiTitle> </EuiTitle>
</EuiFlyoutHeader> </EuiFlyoutHeader>
<EuiFlyoutBody> <EuiFlyoutBody>
<EuiFormRow label="Type"> <EuiFormRow
label={
<FormattedMessage
id="xpack.beatsManagement.tagConfig.typeLabel"
defaultMessage="Type"
/>
}
>
<EuiSelect <EuiSelect
options={supportedConfigs} options={this.supportedConfigs}
value={this.state.configBlock.type} value={this.state.configBlock.type}
disabled={this.editMode} disabled={this.editMode}
onChange={this.onValueChange('type')} onChange={this.onValueChange('type')}
/> />
</EuiFormRow> </EuiFormRow>
<EuiFormRow label="Description"> <EuiFormRow
label={
<FormattedMessage
id="xpack.beatsManagement.tagConfig.descriptionLabel"
defaultMessage="Description"
/>
}
>
<EuiFieldText <EuiFieldText
value={this.state.configBlock.description} value={this.state.configBlock.description}
disabled={!this.props.onSave} disabled={!this.props.onSave}
onChange={this.onValueChange('description')} onChange={this.onValueChange('description')}
placeholder="Description (optional)" placeholder={intl.formatMessage({
id: 'xpack.beatsManagement.tagConfig.descriptionPlaceholder',
defaultMessage: 'Description (optional)',
})}
/> />
</EuiFormRow> </EuiFormRow>
<h3> <h3>
{ <FormattedMessage
(supportedConfigs.find(config => this.state.configBlock.type === config.value) as any) id="xpack.beatsManagement.tagConfig.configurationTypeText"
.text defaultMessage="{configType} configuration"
} values={{
&nbsp;configuration configType: (this.supportedConfigs.find(
config => this.state.configBlock.type === config.value
) as any).text,
}}
/>
</h3> </h3>
<EuiHorizontalRule /> <EuiHorizontalRule />
@ -119,12 +157,14 @@ export class ConfigView extends React.Component<ComponentProps, any> {
ref={this.form} ref={this.form}
values={this.state.configBlock} values={this.state.configBlock}
id={ id={
(supportedConfigs.find(config => this.state.configBlock.type === config.value) as any) (this.supportedConfigs.find(
.value config => this.state.configBlock.type === config.value
) as any).value
} }
schema={ schema={
(supportedConfigs.find(config => this.state.configBlock.type === config.value) as any) (this.supportedConfigs.find(
.config config => this.state.configBlock.type === config.value
) as any).config
} }
/> />
</EuiFlyoutBody> </EuiFlyoutBody>
@ -132,7 +172,10 @@ export class ConfigView extends React.Component<ComponentProps, any> {
<EuiFlexGroup justifyContent="spaceBetween"> <EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={this.props.onClose}> <EuiButtonEmpty iconType="cross" onClick={this.props.onClose}>
Close <FormattedMessage
id="xpack.beatsManagement.tagConfig.closeButtonLabel"
defaultMessage="Close"
/>
</EuiButtonEmpty> </EuiButtonEmpty>
</EuiFlexItem> </EuiFlexItem>
{this.props.onSave && ( {this.props.onSave && (
@ -141,12 +184,15 @@ export class ConfigView extends React.Component<ComponentProps, any> {
disabled={!this.state.valid} disabled={!this.state.valid}
fill fill
onClick={() => { onClick={() => {
if (this.form.current) { if (this.form.current && this.form.current.getWrappedInstance()) {
this.form.current.submit(); this.form.current.getWrappedInstance().submit();
} }
}} }}
> >
Save <FormattedMessage
id="xpack.beatsManagement.tagConfig.saveButtonLabel"
defaultMessage="Save"
/>
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
)} )}
@ -156,3 +202,5 @@ export class ConfigView extends React.Component<ComponentProps, any> {
); );
} }
} }
export const ConfigView = injectI18n(ConfigViewUi);

View file

@ -19,6 +19,7 @@ import {
EuiText, EuiText,
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import 'brace/mode/yaml'; import 'brace/mode/yaml';
import 'brace/theme/github'; import 'brace/theme/github';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
@ -37,6 +38,7 @@ interface TagEditProps {
onDetachBeat: (beatIds: string[]) => void; onDetachBeat: (beatIds: string[]) => void;
onTagChange: (field: keyof BeatTag, value: string) => any; onTagChange: (field: keyof BeatTag, value: string) => any;
attachedBeats: CMBeat[] | null; attachedBeats: CMBeat[] | null;
intl: InjectedIntl;
} }
interface TagEditState { interface TagEditState {
@ -45,7 +47,7 @@ interface TagEditState {
selectedConfigIndex?: number; selectedConfigIndex?: number;
} }
export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> { class TagEditUi extends React.PureComponent<TagEditProps, TagEditState> {
constructor(props: TagEditProps) { constructor(props: TagEditProps) {
super(props); super(props);
@ -56,17 +58,25 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
} }
public render() { public render() {
const { tag, attachedBeats } = this.props; const { tag, attachedBeats, intl } = this.props;
return ( return (
<div> <div>
<EuiFlexGroup> <EuiFlexGroup>
<EuiFlexItem> <EuiFlexItem>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3>Tag details</h3> <h3>
<FormattedMessage
id="xpack.beatsManagement.tag.tagDetailsTitle"
defaultMessage="Tag details"
/>
</h3>
</EuiTitle> </EuiTitle>
<EuiText color="subdued"> <EuiText color="subdued">
<p> <p>
A tag is a group of configuration blocks that you can apply to one or more Beats. <FormattedMessage
id="xpack.beatsManagement.tag.tagDetailsDescription"
defaultMessage="A tag is a group of configuration blocks that you can apply to one or more Beats."
/>
</p> </p>
</EuiText> </EuiText>
<div> <div>
@ -76,7 +86,12 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
<EuiFlexItem> <EuiFlexItem>
<EuiForm> <EuiForm>
<EuiFormRow <EuiFormRow
label="Tag Name" label={
<FormattedMessage
id="xpack.beatsManagement.tag.tagNameLabel"
defaultMessage="Tag Name"
/>
}
isInvalid={!!this.getNameError(tag.id)} isInvalid={!!this.getNameError(tag.id)}
error={this.getNameError(tag.id) || undefined} error={this.getNameError(tag.id) || undefined}
> >
@ -86,11 +101,19 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
onChange={this.updateTag('id')} onChange={this.updateTag('id')}
disabled={this.props.mode === 'edit'} disabled={this.props.mode === 'edit'}
value={tag.id} value={tag.id}
placeholder="Tag name (required)" placeholder={intl.formatMessage({
id: 'xpack.beatsManagement.tag.tagNamePlaceholder',
defaultMessage: 'Tag name (required)',
})}
/> />
</EuiFormRow> </EuiFormRow>
{this.props.mode === 'create' && ( {this.props.mode === 'create' && (
<EuiFormRow label="Tag Color"> <EuiFormRow
label={intl.formatMessage({
id: 'xpack.beatsManagement.tag.tagColorLabel',
defaultMessage: 'Tag Color',
})}
>
<EuiColorPicker color={tag.color} onChange={this.updateTag('color')} /> <EuiColorPicker color={tag.color} onChange={this.updateTag('color')} />
</EuiFormRow> </EuiFormRow>
)} )}
@ -107,13 +130,20 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
> >
<EuiFlexItem> <EuiFlexItem>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3>Configuration blocks</h3> <h3>
<FormattedMessage
id="xpack.beatsManagement.tag.tagConfigurationsTitle"
defaultMessage="Configuration blocks"
/>
</h3>
</EuiTitle> </EuiTitle>
<EuiText color="subdued"> <EuiText color="subdued">
<p> <p>
A tag can have configuration blocks for different types of Beats. For example, a tag <FormattedMessage
can have two Metricbeat configuration blocks and one Filebeat input configuration id="xpack.beatsManagement.tag.tagConfigurationsDescription"
block. defaultMessage="A tag can have configuration blocks for different types of Beats. For example, a tag
can have two Metricbeat configuration blocks and one Filebeat input configuration block."
/>
</p> </p>
</EuiText> </EuiText>
</EuiFlexItem> </EuiFlexItem>
@ -143,7 +173,10 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
this.setState({ showFlyout: true }); this.setState({ showFlyout: true });
}} }}
> >
Add configuration block <FormattedMessage
id="xpack.beatsManagement.tag.addConfigurationButtonLabel"
defaultMessage="Add configuration block"
/>
</EuiButton> </EuiButton>
</div> </div>
</EuiFlexItem> </EuiFlexItem>
@ -154,7 +187,12 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
<EuiHorizontalRule /> <EuiHorizontalRule />
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3>Beats with this tag</h3> <h3>
<FormattedMessage
id="xpack.beatsManagement.tag.beatsAssignedToTagTitle"
defaultMessage="Beats with this tag"
/>
</h3>
</EuiTitle> </EuiTitle>
<Table <Table
assignmentOptions={{ assignmentOptions={{
@ -198,8 +236,12 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
} }
private getNameError = (name: string) => { private getNameError = (name: string) => {
const { intl } = this.props;
if (name && name !== '' && name.search(/^[a-zA-Z0-9-]+$/) === -1) { if (name && name !== '' && name.search(/^[a-zA-Z0-9-]+$/) === -1) {
return 'Tag name must consist of letters, numbers, and dashes only'; return intl.formatMessage({
id: 'xpack.beatsManagement.tag.tagName.validationErrorMessage',
defaultMessage: 'Tag name must consist of letters, numbers, and dashes only',
});
} else { } else {
return false; return false;
} }
@ -219,3 +261,5 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
? this.props.onTagChange(key, value) ? this.props.onTagChange(key, value)
: (e: any) => this.props.onTagChange(key, e.target ? e.target.value : e); : (e: any) => this.props.onTagChange(key, e.target ? e.target.value : e);
} }
export const TagEdit = injectI18n(TagEditUi);

View file

@ -10,24 +10,24 @@ const filebeatInputConfig: YamlConfigSchema[] = [
{ {
id: 'paths', id: 'paths',
ui: { ui: {
label: 'Paths', label: 'filebeatInputConfig.paths.ui.label',
type: 'multi-input', type: 'multi-input',
helpText: 'Put each of the paths on a seperate line', helpText: 'filebeatInputConfig.paths.ui.helpText',
placeholder: `first/path/to/file.json second/path/to/otherfile.json`, placeholder: `first/path/to/file.json second/path/to/otherfile.json`,
}, },
validations: 'isPaths', validations: 'isPaths',
error: 'One file path per line', error: 'filebeatInputConfig.paths.error',
required: true, required: true,
}, },
{ {
id: 'other', id: 'other',
ui: { ui: {
label: 'Other Config', label: 'filebeatInputConfig.other.ui.label',
type: 'code', type: 'code',
helpText: 'Use YAML format to specify other settings for the Filebeat Input', helpText: 'filebeatInputConfig.other.ui.helpText',
}, },
validations: 'isYaml', validations: 'isYaml',
error: 'Use valid YAML format', error: 'filebeatInputConfig.other.error',
}, },
]; ];
@ -35,7 +35,7 @@ const filebeatModuleConfig: YamlConfigSchema[] = [
{ {
id: 'module', id: 'module',
ui: { ui: {
label: 'Module', label: 'filebeatModuleConfig.module.ui.label',
type: 'select', type: 'select',
}, },
options: [ options: [
@ -108,18 +108,18 @@ const filebeatModuleConfig: YamlConfigSchema[] = [
text: 'traefik', text: 'traefik',
}, },
], ],
error: 'Please select a module', error: 'filebeatModuleConfig.module.error',
required: true, required: true,
}, },
{ {
id: 'other', id: 'other',
ui: { ui: {
label: 'Other Config', label: 'filebeatModuleConfig.other.ui.label',
type: 'code', type: 'code',
helpText: 'Use YAML format to specify other settings for the Filebeat Module', helpText: 'filebeatModuleConfig.other.ui.helpText',
}, },
validations: 'isYaml', validations: 'isYaml',
error: 'Use valid YAML format', error: 'filebeatModuleConfig.other.error',
}, },
]; ];
@ -127,7 +127,7 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [
{ {
id: 'module', id: 'module',
ui: { ui: {
label: 'Module', label: 'metricbeatModuleConfig.module.ui.label',
type: 'select', type: 'select',
}, },
options: [ options: [
@ -272,41 +272,41 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [
text: 'zookeeper', text: 'zookeeper',
}, },
], ],
error: 'Please select a module', error: 'metricbeatModuleConfig.module.error',
required: true, required: true,
}, },
{ {
id: 'hosts', id: 'hosts',
ui: { ui: {
label: 'Hosts', label: 'metricbeatModuleConfig.hosts.ui.label',
type: 'multi-input', type: 'multi-input',
helpText: 'Put each of the paths on a seperate line', helpText: 'metricbeatModuleConfig.hosts.ui.helpText',
placeholder: `somehost.local otherhost.local`, placeholder: `somehost.local otherhost.local`,
}, },
validations: 'isHosts', validations: 'isHosts',
error: 'One file host per line', error: 'metricbeatModuleConfig.hosts.error',
required: false, required: false,
}, },
{ {
id: 'period', id: 'period',
ui: { ui: {
label: 'Period', label: 'metricbeatModuleConfig.period.ui.label',
type: 'input', type: 'input',
}, },
defaultValue: '10s', defaultValue: '10s',
validations: 'isPeriod', validations: 'isPeriod',
error: 'Invalid Period, must be formatted as `10s` for 10 seconds', error: 'metricbeatModuleConfig.period.error',
required: true, required: true,
}, },
{ {
id: 'other', id: 'other',
ui: { ui: {
label: 'Other Config', label: 'metricbeatModuleConfig.other.ui.label',
type: 'code', type: 'code',
helpText: 'Use YAML format to specify other settings for the Metricbeat Module', helpText: 'metricbeatModuleConfig.other.ui.helpText',
}, },
validations: 'isYaml', validations: 'isYaml',
error: 'Use valid YAML format', error: 'metricbeatModuleConfig.other.error',
}, },
]; ];
@ -314,7 +314,7 @@ const outputConfig: YamlConfigSchema[] = [
{ {
id: 'output', id: 'output',
ui: { ui: {
label: 'Output Type', label: 'outputConfig.output.ui.label',
type: 'select', type: 'select',
}, },
options: [ options: [
@ -335,42 +335,54 @@ const outputConfig: YamlConfigSchema[] = [
text: 'Console', text: 'Console',
}, },
], ],
error: 'Please select an output type', error: 'outputConfig.output.error',
required: true, required: true,
}, },
{ {
id: '{{output}}.hosts', id: '{{output}}.hosts',
ui: { ui: {
label: 'Hosts', label: 'outputConfig.hosts.ui.label',
type: 'multi-input', type: 'multi-input',
}, },
validations: 'isHosts', validations: 'isHosts',
error: 'One file host per line', error: 'outputConfig.hosts.error',
parseValidResult: v => v.split('\n'), parseValidResult: v => v.split('\n'),
}, },
{ {
id: '{{output}}.username', id: '{{output}}.username',
ui: { ui: {
label: 'Username', label: 'outputConfig.username.ui.label',
type: 'input', type: 'input',
}, },
validations: 'isString', validations: 'isString',
error: 'Unprocessable username', error: 'outputConfig.username.error',
}, },
{ {
id: '{{output}}.password', id: '{{output}}.password',
ui: { ui: {
label: 'Password', label: 'outputConfig.password.ui.label',
type: 'password', type: 'password',
}, },
validations: 'isString', validations: 'isString',
error: 'Unprocessable password', error: 'outputConfig.password.error',
}, },
]; ];
export const supportedConfigs = [ export const supportedConfigs = [
{ text: 'Filebeat Input', value: 'filebeat.inputs', config: filebeatInputConfig }, {
{ text: 'Filebeat Module', value: 'filebeat.modules', config: filebeatModuleConfig }, text: 'supportedConfigs.filebeatInput.text',
{ text: 'Metricbeat Module', value: 'metricbeat.modules', config: metricbeatModuleConfig }, value: 'filebeat.inputs',
{ text: 'Output', value: 'output', config: outputConfig }, config: filebeatInputConfig,
},
{
text: 'supportedConfigs.filebeatModule.text',
value: 'filebeat.modules',
config: filebeatModuleConfig,
},
{
text: 'supportedConfigs.metricbeatModule.text',
value: 'metricbeat.modules',
config: metricbeatModuleConfig,
},
{ text: 'supportedConfigs.output.text', value: 'output', config: outputConfig },
]; ];

View file

@ -0,0 +1,249 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { cloneDeep } from 'lodash';
import { supportedConfigs } from './config_schemas';
import { YamlConfigSchema } from './lib/lib';
interface ConfigSchema {
text: string;
value: string;
config: YamlConfigSchema[];
}
let translatedConfigs: ConfigSchema[];
const supportedConfigLabelsMap = new Map<string, string>([
[
'filebeatInputConfig.paths.ui.label',
i18n.translate('xpack.beatsManagement.filebeatInputConfig.pathsLabel', {
defaultMessage: 'Paths',
}),
],
[
'filebeatInputConfig.paths.ui.helpText',
i18n.translate('xpack.beatsManagement.filebeatInputConfig.pathsDescription', {
defaultMessage: 'Put each of the paths on a separate line',
}),
],
[
'filebeatInputConfig.paths.error',
i18n.translate('xpack.beatsManagement.filebeatInputConfig.pathsErrorMessage', {
defaultMessage: 'One file path per line',
}),
],
[
'filebeatInputConfig.other.ui.label',
i18n.translate('xpack.beatsManagement.filebeatInputConfig.otherConfigLabel', {
defaultMessage: 'Other Config',
}),
],
[
'filebeatInputConfig.other.ui.helpText',
i18n.translate('xpack.beatsManagement.filebeatInputConfig.otherConfigDescription', {
defaultMessage: 'Use YAML format to specify other settings for the Filebeat Input',
}),
],
[
'filebeatInputConfig.other.error',
i18n.translate('xpack.beatsManagement.filebeatInputConfig.otherConfigErrorMessage', {
defaultMessage: 'Use valid YAML format',
}),
],
[
'filebeatModuleConfig.module.ui.label',
i18n.translate('xpack.beatsManagement.filebeatModuleConfig.moduleLabel', {
defaultMessage: 'Module',
}),
],
[
'filebeatModuleConfig.module.error',
i18n.translate('xpack.beatsManagement.filebeatModuleConfig.moduleErrorMessage', {
defaultMessage: 'Please select a module',
}),
],
[
'filebeatModuleConfig.other.ui.label',
i18n.translate('xpack.beatsManagement.filebeatModuleConfig.otherConfigLabel', {
defaultMessage: 'Other Config',
}),
],
[
'filebeatModuleConfig.other.ui.helpText',
i18n.translate('xpack.beatsManagement.filebeatModuleConfig.moduleDescription', {
defaultMessage: 'Use YAML format to specify other settings for the Filebeat Module',
}),
],
[
'filebeatModuleConfig.other.error',
i18n.translate('xpack.beatsManagement.filebeatModuleConfig.otherConfigErrorMessage', {
defaultMessage: 'Use valid YAML format',
}),
],
[
'metricbeatModuleConfig.module.ui.label',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.moduleLabel', {
defaultMessage: 'Module',
}),
],
[
'metricbeatModuleConfig.module.error',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.moduleErrorMessage', {
defaultMessage: 'Please select a module',
}),
],
[
'metricbeatModuleConfig.hosts.ui.label',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.hostsLabel', {
defaultMessage: 'Hosts',
}),
],
[
'metricbeatModuleConfig.hosts.ui.helpText',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.hostsDescription', {
defaultMessage: 'Put each of the paths on a seperate line',
}),
],
[
'metricbeatModuleConfig.hosts.error',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.hostsErrorMessage', {
defaultMessage: 'One file host per line',
}),
],
[
'metricbeatModuleConfig.period.ui.label',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.periodLabel', {
defaultMessage: 'Period',
}),
],
[
'metricbeatModuleConfig.period.error',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.periodErrorMessage', {
defaultMessage: 'Invalid Period, must be formatted as `10s` for 10 seconds',
}),
],
[
'metricbeatModuleConfig.other.ui.label',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.otherConfigLabel', {
defaultMessage: 'Other Config',
}),
],
[
'metricbeatModuleConfig.other.ui.helpText',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.otherConfigDescription', {
defaultMessage: 'Use YAML format to specify other settings for the Metricbeat Module',
}),
],
[
'metricbeatModuleConfig.other.error',
i18n.translate('xpack.beatsManagement.metricbeatModuleConfig.otherConfigErrorMessage', {
defaultMessage: 'Use valid YAML format',
}),
],
[
'outputConfig.output.ui.label',
i18n.translate('xpack.beatsManagement.outputConfig.outputTypeLabel', {
defaultMessage: 'Output Type',
}),
],
[
'outputConfig.output.error',
i18n.translate('xpack.beatsManagement.outputConfig.outputTypeErrorMessage', {
defaultMessage: 'Please select an output type',
}),
],
[
'outputConfig.hosts.ui.label',
i18n.translate('xpack.beatsManagement.outputConfig.hostsLabel', {
defaultMessage: 'Hosts',
}),
],
[
'outputConfig.hosts.error',
i18n.translate('xpack.beatsManagement.outputConfig.hostsErrorMessage', {
defaultMessage: 'One file host per line',
}),
],
[
'outputConfig.username.ui.label',
i18n.translate('xpack.beatsManagement.outputConfig.usernameLabel', {
defaultMessage: 'Username',
}),
],
[
'outputConfig.username.error',
i18n.translate('xpack.beatsManagement.outputConfig.usernameErrorMessage', {
defaultMessage: 'Unprocessable username',
}),
],
[
'outputConfig.password.ui.label',
i18n.translate('xpack.beatsManagement.outputConfig.passwordLabel', {
defaultMessage: 'Password',
}),
],
[
'outputConfig.password.error',
i18n.translate('xpack.beatsManagement.outputConfig.passwordErrorMessage', {
defaultMessage: 'Unprocessable password',
}),
],
[
'supportedConfigs.filebeatInput.text',
i18n.translate('xpack.beatsManagement.tagConfig.filebeatInputLabel', {
defaultMessage: 'Filebeat Input',
}),
],
[
'supportedConfigs.filebeatModule.text',
i18n.translate('xpack.beatsManagement.tagConfig.filebeatModuleLabel', {
defaultMessage: 'Filebeat Module',
}),
],
[
'supportedConfigs.metricbeatModule.text',
i18n.translate('xpack.beatsManagement.tagConfig.metricbeatModuleLabel', {
defaultMessage: 'Metricbeat Module',
}),
],
[
'supportedConfigs.output.text',
i18n.translate('xpack.beatsManagement.tagConfig.outputLabel', {
defaultMessage: 'Output',
}),
],
]);
export const getSupportedConfig = () => {
if (translatedConfigs) {
return translatedConfigs;
}
translatedConfigs = cloneDeep(supportedConfigs);
translatedConfigs.forEach(({ text, config }) => {
if (text) {
text = supportedConfigLabelsMap.get(text) || '';
}
config.forEach(yanlConfig => {
if (yanlConfig.ui.label) {
yanlConfig.ui.label = supportedConfigLabelsMap.get(yanlConfig.ui.label) || '';
}
if (yanlConfig.ui.helpText) {
yanlConfig.ui.helpText = supportedConfigLabelsMap.get(yanlConfig.ui.helpText);
}
if (yanlConfig.error) {
yanlConfig.error = supportedConfigLabelsMap.get(yanlConfig.error) || '';
}
});
});
return translatedConfigs;
};

View file

@ -5,6 +5,8 @@
*/ */
import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { BASE_PATH } from '../common/constants'; import { BASE_PATH } from '../common/constants';
@ -14,13 +16,21 @@ import { FrontendLibs } from './lib/lib';
import { PageRouter } from './router'; import { PageRouter } from './router';
function startApp(libs: FrontendLibs) { function startApp(libs: FrontendLibs) {
libs.framework.registerManagementSection('beats', 'Central Management (Beta)', BASE_PATH); libs.framework.registerManagementSection(
'beats',
i18n.translate('xpack.beatsManagement.managementMainPage.centralManagementLinkLabel', {
defaultMessage: 'Central Management (Beta)',
}),
BASE_PATH
);
libs.framework.render( libs.framework.render(
<ThemeProvider theme={{ eui: euiVars }}> <I18nProvider>
<BreadcrumbProvider> <ThemeProvider theme={{ eui: euiVars }}>
<PageRouter libs={libs} /> <BreadcrumbProvider>
</BreadcrumbProvider> <PageRouter libs={libs} />
</ThemeProvider> </BreadcrumbProvider>
</ThemeProvider>
</I18nProvider>
); );
} }

View file

@ -8,6 +8,7 @@ import { IModule, IScope } from 'angular';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { import {
BufferedKibanaServiceCall, BufferedKibanaServiceCall,
FrameworkAdapter, FrameworkAdapter,
@ -103,7 +104,9 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
if (this.hasValidLicense()) { if (this.hasValidLicense()) {
const registerSection = () => const registerSection = () =>
this.management.register(pluginId, { this.management.register(pluginId, {
display: 'Beats', // TODO these need to be config options not hard coded in the adapter display: i18n.translate('xpack.beatsManagement.beatsDislayName', {
defaultMessage: 'Beats',
}), // TODO these need to be config options not hard coded in the adapter
icon: 'logoBeats', icon: 'logoBeats',
order: 30, order: 30,
}); });

View file

@ -18,7 +18,7 @@ import { uiModules } from 'ui/modules';
import routes from 'ui/routes'; import routes from 'ui/routes';
import { INDEX_NAMES } from '../../../common/constants/index_names'; import { INDEX_NAMES } from '../../../common/constants/index_names';
import { supportedConfigs } from '../../config_schemas'; import { getSupportedConfig } from '../../config_schemas_translations_map';
import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter'; import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter';
import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest'; import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest';
import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
@ -34,7 +34,7 @@ export function compose(): FrontendLibs {
const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath());
const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.BEATS); const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.BEATS);
const tags = new TagsLib(new RestTagsAdapter(api), supportedConfigs); const tags = new TagsLib(new RestTagsAdapter(api), getSupportedConfig());
const tokens = new RestTokensAdapter(api); const tokens = new RestTokensAdapter(api);
const beats = new BeatsLib(new RestBeatsAdapter(api), { const beats = new BeatsLib(new RestBeatsAdapter(api), {
tags, tags,

View file

@ -21,7 +21,7 @@ import { BeatsLib } from '../beats';
import { FrontendDomainLibs, FrontendLibs } from '../lib'; import { FrontendDomainLibs, FrontendLibs } from '../lib';
import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; import { AutocompleteSuggestion } from 'ui/autocomplete_providers';
import { supportedConfigs } from '../../config_schemas'; import { getSupportedConfig } from '../../config_schemas_translations_map';
import { TagsLib } from '../tags'; import { TagsLib } from '../tags';
import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory';
import { ElasticsearchLib } from './../elasticsearch'; import { ElasticsearchLib } from './../elasticsearch';
@ -36,7 +36,7 @@ export function compose(
mockKueryToEsQuery, mockKueryToEsQuery,
suggestions suggestions
); );
const tags = new TagsLib(new MemoryTagsAdapter([]), supportedConfigs); const tags = new TagsLib(new MemoryTagsAdapter([]), getSupportedConfig());
const tokens = new MemoryTokensAdapter(); const tokens = new MemoryTokensAdapter();
const beats = new BeatsLib(new MemoryBeatsAdapter([]), { tags }); const beats = new BeatsLib(new MemoryBeatsAdapter([]), { tags });

View file

@ -4,10 +4,18 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
export class NotFoundPage extends React.PureComponent { export class NotFoundPage extends React.PureComponent {
public render() { public render() {
return <div>No content found</div>; return (
<div>
<FormattedMessage
id="xpack.beatsManagement.noContentFoundErrorMessage"
defaultMessage="No content found"
/>
</div>
);
} }
} }

View file

@ -5,6 +5,7 @@
*/ */
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { first, sortByOrder } from 'lodash'; import { first, sortByOrder } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import React from 'react'; import React from 'react';
@ -20,14 +21,20 @@ export const BeatDetailsActionSection = ({ beat }: BeatDetailsActionSectionProps
<EuiFlexGroup> <EuiFlexGroup>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiText size="xs"> <EuiText size="xs">
Type:&nbsp; <FormattedMessage
<strong>{beat.type}</strong>. id="xpack.beatsManagement.beat.actionSectionTypeLabel"
defaultMessage="Type: {beatType}."
values={{ beatType: <strong>{beat.type}</strong> }}
/>
</EuiText> </EuiText>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiText size="xs"> <EuiText size="xs">
Version:&nbsp; <FormattedMessage
<strong>{beat.version}</strong>. id="xpack.beatsManagement.beat.actionSectionVersionLabel"
defaultMessage="Version: {beatVersion}."
values={{ beatVersion: <strong>{beat.version}</strong> }}
/>
</EuiText> </EuiText>
</EuiFlexItem> </EuiFlexItem>
{/* TODO: We need a populated field before we can run this code {/* TODO: We need a populated field before we can run this code
@ -40,19 +47,30 @@ export const BeatDetailsActionSection = ({ beat }: BeatDetailsActionSectionProps
beat.full_tags.length > 0 && ( beat.full_tags.length > 0 && (
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiText size="xs"> <EuiText size="xs">
Last Config Update:{' '} <FormattedMessage
<strong> id="xpack.beatsManagement.beat.lastConfigUpdateMessage"
{moment( defaultMessage="Last Config Update: {lastUpdateTime}."
first(sortByOrder(beat.full_tags, 'last_updated')).last_updated values={{
).fromNow()} lastUpdateTime: (
</strong> <strong>
. {moment(
first(sortByOrder(beat.full_tags, 'last_updated')).last_updated
).fromNow()}
</strong>
),
}}
/>
</EuiText> </EuiText>
</EuiFlexItem> </EuiFlexItem>
)} )}
</EuiFlexGroup> </EuiFlexGroup>
) : ( ) : (
<div>Beat not found</div> <div>
<FormattedMessage
id="xpack.beatsManagement.beat.beatNotFoundMessage"
defaultMessage="Beat not found"
/>
</div>
)} )}
</div> </div>
); );

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { FrontendLibs } from '../../lib/lib'; import { FrontendLibs } from '../../lib/lib';
@ -11,4 +12,11 @@ interface BeatActivityPageProps {
libs: FrontendLibs; libs: FrontendLibs;
} }
export const BeatActivityPage = (props: BeatActivityPageProps) => <div>Beat Activity View</div>; export const BeatActivityPage = (props: BeatActivityPageProps) => (
<div>
<FormattedMessage
id="xpack.beatsManagement.beat.beatActivityViewTitle"
defaultMessage="Beat Activity View"
/>
</div>
);

View file

@ -13,6 +13,7 @@ import {
EuiText, EuiText,
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { flatten, get } from 'lodash'; import { flatten, get } from 'lodash';
import React from 'react'; import React from 'react';
import { TABLE_CONFIG } from '../../../common/constants'; import { TABLE_CONFIG } from '../../../common/constants';
@ -20,17 +21,18 @@ import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/do
import { ConnectedLink } from '../../components/connected_link'; import { ConnectedLink } from '../../components/connected_link';
import { TagBadge } from '../../components/tag'; import { TagBadge } from '../../components/tag';
import { ConfigView } from '../../components/tag/config_view/index'; import { ConfigView } from '../../components/tag/config_view/index';
import { supportedConfigs } from '../../config_schemas'; import { getSupportedConfig } from '../../config_schemas_translations_map';
interface PageProps { interface PageProps {
beat: CMPopulatedBeat | undefined; beat: CMPopulatedBeat | undefined;
intl: InjectedIntl;
} }
interface PageState { interface PageState {
selectedConfig: ConfigurationBlock | null; selectedConfig: ConfigurationBlock | null;
} }
export class BeatDetailPage extends React.PureComponent<PageProps, PageState> { class BeatDetailPageUi extends React.PureComponent<PageProps, PageState> {
constructor(props: PageProps) { constructor(props: PageProps) {
super(props); super(props);
@ -40,9 +42,16 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
} }
public render() { public render() {
const props = this.props; const props = this.props;
const { beat } = props; const { beat, intl } = props;
if (!beat) { if (!beat) {
return <div>Beat not found</div>; return (
<div>
<FormattedMessage
id="xpack.beatsManagement.beat.beatNotFoundErrorTitle"
defaultMessage="Beat not found"
/>
</div>
);
} }
const configurationBlocks = flatten( const configurationBlocks = flatten(
beat.full_tags.map((tag: BeatTag) => { beat.full_tags.map((tag: BeatTag) => {
@ -54,7 +63,7 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
...beat, ...beat,
...configuration, ...configuration,
displayValue: get( displayValue: get(
supportedConfigs.find(config => config.value === configuration.type), getSupportedConfig().find(config => config.value === configuration.type),
'text', 'text',
null null
), ),
@ -65,7 +74,10 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
const columns = [ const columns = [
{ {
field: 'displayValue', field: 'displayValue',
name: 'Type', name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.typeColumnName',
defaultMessage: 'Type',
}),
sortable: true, sortable: true,
render: (value: string | null, configuration: any) => ( render: (value: string | null, configuration: any) => (
<EuiLink <EuiLink
@ -81,17 +93,26 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
}, },
{ {
field: 'module', field: 'module',
name: 'Module', name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.moduleColumnName',
defaultMessage: 'Module',
}),
sortable: true, sortable: true,
}, },
{ {
field: 'description', field: 'description',
name: 'Description', name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.descriptionColumnName',
defaultMessage: 'Description',
}),
sortable: true, sortable: true,
}, },
{ {
field: 'tagId', field: 'tagId',
name: 'Tag', name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.tagColumnName',
defaultMessage: 'Tag',
}),
render: (id: string, block: any) => ( render: (id: string, block: any) => (
<ConnectedLink path={`/tag/edit/${id}`}> <ConnectedLink path={`/tag/edit/${id}`}>
<TagBadge <TagBadge
@ -108,13 +129,21 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
<EuiFlexGroup> <EuiFlexGroup>
<EuiFlexItem> <EuiFlexItem>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h4>Configurations</h4> <h4>
<FormattedMessage
id="xpack.beatsManagement.beat.detailsConfigurationTitle"
defaultMessage="Configurations"
/>
</h4>
</EuiTitle> </EuiTitle>
<EuiText size="s"> <EuiText size="s">
<p> <p>
You can have multiple configurations applied to an individual tag. These <FormattedMessage
configurations can repeat or mix types as necessary. For example, you may utilize id="xpack.beatsManagement.beat.detailsConfigurationDescription"
three metricbeat configurations alongside one input and filebeat configuration. defaultMessage="You can have multiple configurations applied to an individual tag. These configurations can repeat
or mix types as necessary. For example, you may utilize three metricbeat configurations alongside one input and
filebeat configuration."
/>
</p> </p>
</EuiText> </EuiText>
</EuiFlexItem> </EuiFlexItem>
@ -132,3 +161,4 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
); );
} }
} }
export const BeatDetailPage = injectI18n(BeatDetailPageUi);

View file

@ -11,6 +11,7 @@ import {
// @ts-ignore types for EuiTabs not currently available // @ts-ignore types for EuiTabs not currently available
EuiTabs, EuiTabs,
} from '@elastic/eui'; } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import { CMPopulatedBeat } from '../../../common/domain_types'; import { CMPopulatedBeat } from '../../../common/domain_types';
@ -32,6 +33,7 @@ interface BeatDetailsPageProps extends URLStateProps<AppURLState> {
history: any; history: any;
libs: FrontendLibs; libs: FrontendLibs;
match: Match; match: Match;
intl: InjectedIntl;
} }
interface BeatDetailsPageState { interface BeatDetailsPageState {
@ -63,6 +65,7 @@ class BeatDetailsPageComponent extends React.PureComponent<
}; };
public render() { public render() {
const { intl } = this.props;
const { beat } = this.state; const { beat } = this.state;
let id; let id;
let name; let name;
@ -72,13 +75,33 @@ class BeatDetailsPageComponent extends React.PureComponent<
name = beat.name; name = beat.name;
} }
const title = this.state.isLoading const title = this.state.isLoading
? 'Loading' ? intl.formatMessage({
: `Beat: ${name || 'No name receved from beat'} (id: ${id})`; id: 'xpack.beatsManagement.beat.loadingTitle',
defaultMessage: 'Loading',
})
: intl.formatMessage(
{
id: 'xpack.beatsManagement.beat.beatNameAndIdTitle',
defaultMessage: 'Beat: {nameOrNoName} (id: {id})',
},
{
nameOrNoName:
name ||
intl.formatHTMLMessage({
id: 'xpack.beatsManagement.beat.noNameReceivedFromBeatTitle',
defaultMessage: 'No name received from beat',
}),
id,
}
);
const tabs = [ const tabs = [
{ {
id: `/beat/${id}`, id: `/beat/${id}`,
name: 'Config', name: intl.formatMessage({
id: 'xpack.beatsManagement.beat.configTabLabel',
defaultMessage: 'Config',
}),
disabled: false, disabled: false,
}, },
// { // {
@ -88,7 +111,10 @@ class BeatDetailsPageComponent extends React.PureComponent<
// }, // },
{ {
id: `/beat/${id}/tags`, id: `/beat/${id}/tags`,
name: 'Configuration Tags', name: intl.formatMessage({
id: 'xpack.beatsManagement.beat.configurationTagsTabLabel',
defaultMessage: 'Configuration Tags',
}),
disabled: false, disabled: false,
}, },
]; ];
@ -141,12 +167,18 @@ class BeatDetailsPageComponent extends React.PureComponent<
} }
private async loadBeat() { private async loadBeat() {
const { intl } = this.props;
const { beatId } = this.props.match.params; const { beatId } = this.props.match.params;
let beat; let beat;
try { try {
beat = await this.props.libs.beats.get(beatId); beat = await this.props.libs.beats.get(beatId);
if (!beat) { if (!beat) {
throw new Error('beat not found'); throw new Error(
intl.formatMessage({
id: 'xpack.beatsManagement.beat.beatNotFoundErrorMessage',
defaultMessage: 'beat not found',
})
);
} }
} catch (e) { } catch (e) {
throw new Error(e); throw new Error(e);
@ -154,4 +186,6 @@ class BeatDetailsPageComponent extends React.PureComponent<
this.setState({ beat, isLoading: false }); this.setState({ beat, isLoading: false });
} }
} }
export const BeatDetailsPage = withUrlState<BeatDetailsPageProps>(BeatDetailsPageComponent); const BeatDetailsPageUi = withUrlState<BeatDetailsPageProps>(BeatDetailsPageComponent);
export const BeatDetailsPage = injectI18n(BeatDetailsPageUi);

View file

@ -3,11 +3,24 @@
* or more contributor license agreements. Licensed under the Elastic License; * or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import * as React from 'react'; import * as React from 'react';
import { NoDataLayout } from '../components/layouts/no_data'; import { NoDataLayout } from '../components/layouts/no_data';
export const EnforceSecurityPage: React.SFC<any> = () => ( export const EnforceSecurityPage = injectI18n(({ intl }) => (
<NoDataLayout title="Security is not enabled" actionSection={[]}> <NoDataLayout
<p>You must enable security in Kibana and Elasticsearch to use Beats central management.</p> title={intl.formatMessage({
id: 'xpack.beatsManagement.disabledSecurityTitle',
defaultMessage: 'Security is not enabled',
})}
actionSection={[]}
>
<p>
<FormattedMessage
id="xpack.beatsManagement.disabledSecurityDescription"
defaultMessage="You must enable security in Kibana and Elasticsearch to use Beats central management."
/>
</p>
</NoDataLayout> </NoDataLayout>
); ));

View file

@ -3,14 +3,25 @@
* or more contributor license agreements. Licensed under the Elastic License; * or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import * as React from 'react'; import * as React from 'react';
import { NoDataLayout } from '../components/layouts/no_data'; import { NoDataLayout } from '../components/layouts/no_data';
export const InvalidLicensePage: React.SFC<any> = () => ( export const InvalidLicensePage = injectI18n(({ intl }) => (
<NoDataLayout title="Expired license" actionSection={[]}> <NoDataLayout
title={intl.formatMessage({
id: 'xpack.beatsManagement.invalidLicenseTitle',
defaultMessage: 'Expired license',
})}
actionSection={[]}
>
<p> <p>
Your current license is expired. Enrolled Beats will continue to work, but you need a valid <FormattedMessage
license to access the Beats Management UI. id="xpack.beatsManagement.invalidLicenseDescription"
defaultMessage="Your current license is expired. Enrolled Beats will continue to work, but you need a valid
license to access the Beats Management UI."
/>
</p> </p>
</NoDataLayout> </NoDataLayout>
); ));

View file

@ -4,10 +4,18 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
export class ActivityPage extends React.PureComponent { export class ActivityPage extends React.PureComponent {
public render() { public render() {
return <div>activity logs view</div>; return (
<div>
<FormattedMessage
id="xpack.beatsManagement.activityLogsViewTitle"
defaultMessage="activity logs view"
/>
</div>
);
} }
} }

View file

@ -14,6 +14,7 @@ import {
EuiModalHeaderTitle, EuiModalHeaderTitle,
EuiOverlayMask, EuiOverlayMask,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { flatten, intersection, sortBy } from 'lodash'; import { flatten, intersection, sortBy } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import React from 'react'; import React from 'react';
@ -31,6 +32,7 @@ import { FrontendLibs } from '../../lib/lib';
import { EnrollBeatPage } from './enroll_fragment'; import { EnrollBeatPage } from './enroll_fragment';
interface BeatsPageProps extends URLStateProps<AppURLState> { interface BeatsPageProps extends URLStateProps<AppURLState> {
intl: InjectedIntl;
libs: FrontendLibs; libs: FrontendLibs;
location: any; location: any;
beats: CMPopulatedBeat[]; beats: CMPopulatedBeat[];
@ -47,7 +49,7 @@ interface ActionAreaProps extends URLStateProps<AppURLState>, RouteComponentProp
libs: FrontendLibs; libs: FrontendLibs;
} }
export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageState> { class BeatsPageUi extends React.PureComponent<BeatsPageProps, BeatsPageState> {
public static ActionArea = (props: ActionAreaProps) => ( public static ActionArea = (props: ActionAreaProps) => (
<React.Fragment> <React.Fragment>
<EuiButtonEmpty <EuiButtonEmpty
@ -60,7 +62,10 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
); );
}} }}
> >
Learn how to install beats <FormattedMessage
id="xpack.beatsManagement.beats.installBeatsLearningButtonLabel"
defaultMessage="Learn how to install beats"
/>
</EuiButtonEmpty> </EuiButtonEmpty>
<EuiButton <EuiButton
size="s" size="s"
@ -69,7 +74,10 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
props.goTo(`/overview/beats/enroll`); props.goTo(`/overview/beats/enroll`);
}} }}
> >
Enroll Beats <FormattedMessage
id="xpack.beatsManagement.beats.enrollBeatsButtonLabel"
defaultMessage="Enroll Beats"
/>
</EuiButton> </EuiButton>
{props.location.pathname === '/overview/beats/enroll' && ( {props.location.pathname === '/overview/beats/enroll' && (
@ -81,7 +89,12 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
style={{ width: '640px' }} style={{ width: '640px' }}
> >
<EuiModalHeader> <EuiModalHeader>
<EuiModalHeaderTitle>Enroll a new Beat</EuiModalHeaderTitle> <EuiModalHeaderTitle>
<FormattedMessage
id="xpack.beatsManagement.beats.enrollNewBeatsTitle"
defaultMessage="Enroll a new Beat"
/>
</EuiModalHeaderTitle>
</EuiModalHeader> </EuiModalHeader>
<EuiModalBody> <EuiModalBody>
<EnrollBeatPage {...props} /> <EnrollBeatPage {...props} />
@ -217,13 +230,38 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
}; };
private notifyBeatDisenrolled = async (beats: CMPopulatedBeat[]) => { private notifyBeatDisenrolled = async (beats: CMPopulatedBeat[]) => {
const { intl } = this.props;
let title; let title;
let text; let text;
if (beats.length === 1) { if (beats.length === 1) {
title = `"${beats[0].name || beats[0].id}" disenrolled`; title = intl.formatMessage(
text = `Beat with ID "${beats[0].id}" was disenrolled.`; {
id: 'xpack.beatsManagement.beats.beatDisenrolledNotificationTitle',
defaultMessage: '{firstBeatNameOrId} disenrolled',
},
{
firstBeatNameOrId: `"${beats[0].name || beats[0].id}"`,
}
);
text = intl.formatMessage(
{
id: 'xpack.beatsManagement.beats.beatDisenrolledNotificationDescription',
defaultMessage: 'Beat with ID {firstBeatId} was disenrolled.',
},
{
firstBeatId: `"${beats[0].id}"`,
}
);
} else { } else {
title = `${beats.length} beats disenrolled`; title = intl.formatMessage(
{
id: 'xpack.beatsManagement.beats.disenrolledBeatsNotificationTitle',
defaultMessage: '{beatsLength} beats disenrolled',
},
{
beatsLength: beats.length,
}
);
} }
this.setState({ this.setState({
@ -241,18 +279,59 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
assignments: BeatsTagAssignment[], assignments: BeatsTagAssignment[],
tag: string tag: string
) => { ) => {
const actionName = action === 'remove' ? 'Removed' : 'Added'; const { intl } = this.props;
const preposition = action === 'remove' ? 'from' : 'to'; const notificationMessage =
const beatMessage = action === 'remove'
assignments.length && assignments.length === 1 ? intl.formatMessage(
? `beat "${this.getNameForBeatId(assignments[0].beatId)}"` {
: `${assignments.length} beats`; id: 'xpack.beatsManagement.beats.removedNotificationDescription',
defaultMessage:
'Removed tag {tag} from {assignmentsLength, plural, one {beat {beatName}} other {# beats}}.',
},
{
tag: `"${tag}"`,
assignmentsLength: assignments.length,
beatName: `"${this.getNameForBeatId(assignments[0].beatId)}"`,
}
)
: intl.formatMessage(
{
id: 'xpack.beatsManagement.beats.addedNotificationDescription',
defaultMessage:
'Added tag {tag} to {assignmentsLength, plural, one {beat {beatName}} other {# beats}}.',
},
{
tag: `"${tag}"`,
assignmentsLength: assignments.length,
beatName: `"${this.getNameForBeatId(assignments[0].beatId)}"`,
}
);
const notificationTitle =
action === 'remove'
? intl.formatMessage(
{
id: 'xpack.beatsManagement.beats.removedNotificationTitle',
defaultMessage: '{assignmentsLength, plural, one {Tag} other {Tags}} removed',
},
{
assignmentsLength: assignments.length,
}
)
: intl.formatMessage(
{
id: 'xpack.beatsManagement.beats.addedNotificationTitle',
defaultMessage: '{assignmentsLength, plural, one {Tag} other {Tags}} added',
},
{
assignmentsLength: assignments.length,
}
);
this.setState({ this.setState({
notifications: this.state.notifications.concat({ notifications: this.state.notifications.concat({
color: 'success', color: 'success',
id: `tag-${moment.now()}`, id: `tag-${moment.now()}`,
text: <p>{`${actionName} tag "${tag}" ${preposition} ${beatMessage}.`}</p>, text: <p>{notificationMessage}</p>,
title: `Tag ${actionName}`, title: notificationTitle,
}), }),
}); });
}; };
@ -311,3 +390,8 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
// reduce to result // reduce to result
.reduce((acc, cur) => acc || cur, false); .reduce((acc, cur) => acc || cur, false);
} }
const BeatsPageWrapped = injectI18n(BeatsPageUi);
export const BeatsPage = BeatsPageWrapped as typeof BeatsPageWrapped & {
ActionArea: typeof BeatsPageUi['ActionArea'];
};

View file

@ -5,6 +5,7 @@
*/ */
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import 'brace/mode/yaml'; import 'brace/mode/yaml';
import 'brace/theme/github'; import 'brace/theme/github';
@ -18,6 +19,7 @@ import { FrontendLibs } from '../../lib/lib';
interface TagPageProps extends URLStateProps<AppURLState> { interface TagPageProps extends URLStateProps<AppURLState> {
libs: FrontendLibs; libs: FrontendLibs;
match: any; match: any;
intl: InjectedIntl;
} }
interface TagPageState { interface TagPageState {
@ -25,7 +27,7 @@ interface TagPageState {
tag: BeatTag; tag: BeatTag;
} }
export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPageState> { class CreateTagFragment extends React.PureComponent<TagPageProps, TagPageState> {
private mode: 'edit' | 'create' = 'create'; private mode: 'edit' | 'create' = 'create';
constructor(props: TagPageProps) { constructor(props: TagPageProps) {
super(props); super(props);
@ -79,7 +81,10 @@ export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPage
} }
onClick={this.saveTag} onClick={this.saveTag}
> >
Save & Continue <FormattedMessage
id="xpack.beatsManagement.createTag.saveAndContinueButtonLabel"
defaultMessage="Save & Continue"
/>
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
@ -97,9 +102,15 @@ export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPage
}; };
private saveTag = async () => { private saveTag = async () => {
const { intl } = this.props;
const newTag = await this.props.libs.tags.upsertTag(this.state.tag as BeatTag); const newTag = await this.props.libs.tags.upsertTag(this.state.tag as BeatTag);
if (!newTag) { if (!newTag) {
return alert('error saving tag'); return alert(
intl.formatMessage({
id: 'xpack.beatsManagement.createTag.errorSavingTagTitle',
defaultMessage: 'error saving tag',
})
);
} }
this.props.setUrlState({ this.props.setUrlState({
createdTag: newTag.id, createdTag: newTag.id,
@ -107,4 +118,6 @@ export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPage
this.props.goTo(`/overview/initial/finish`); this.props.goTo(`/overview/initial/finish`);
}; };
} }
export const CreateTagPageFragment = withUrlState<TagPageProps>(CreateTagFragment); const CreateTagPageFragmentUi = withUrlState<TagPageProps>(CreateTagFragment);
export const CreateTagPageFragment = injectI18n(CreateTagPageFragmentUi);

View file

@ -15,6 +15,7 @@ import {
EuiSelect, EuiSelect,
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { capitalize } from 'lodash'; import { capitalize } from 'lodash';
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
@ -26,6 +27,7 @@ import { FrontendLibs } from '../../lib/lib';
interface BeatsProps extends URLStateProps<AppURLState>, RouteComponentProps<any> { interface BeatsProps extends URLStateProps<AppURLState>, RouteComponentProps<any> {
match: any; match: any;
libs: FrontendLibs; libs: FrontendLibs;
intl: InjectedIntl;
} }
export class EnrollBeat extends React.Component<BeatsProps, any> { export class EnrollBeat extends React.Component<BeatsProps, any> {
private pinging = false; private pinging = false;
@ -83,7 +85,7 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
if (this.props.urlState.enrollmentToken && !this.state.enrolledBeat) { if (this.props.urlState.enrollmentToken && !this.state.enrolledBeat) {
this.waitForToken(this.props.urlState.enrollmentToken); this.waitForToken(this.props.urlState.enrollmentToken);
} }
const { goTo } = this.props; const { goTo, intl } = this.props;
const actions = []; const actions = [];
@ -91,18 +93,27 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
case '/overview/initial/beats': case '/overview/initial/beats':
actions.push({ actions.push({
goTo: '/overview/initial/tag', goTo: '/overview/initial/tag',
name: 'Continue', name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.continueButtonLabel',
defaultMessage: 'Continue',
}),
}); });
break; break;
case '/overview/beats/enroll': case '/overview/beats/enroll':
actions.push({ actions.push({
goTo: '/overview/beats/enroll', goTo: '/overview/beats/enroll',
name: 'Enroll another Beat', name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.enrollAnotherBeatButtonLabel',
defaultMessage: 'Enroll another Beat',
}),
newToken: true, newToken: true,
}); });
actions.push({ actions.push({
goTo: '/overview/beats', goTo: '/overview/beats',
name: 'Done', name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.doneButtonLabel',
defaultMessage: 'Done',
}),
clearToken: true, clearToken: true,
}); });
break; break;
@ -117,15 +128,26 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3>Beat type:</h3> <h3>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.beatTypeTitle"
defaultMessage="Beat type:"
/>
</h3>
</EuiTitle> </EuiTitle>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
<EuiSelect <EuiSelect
value={this.state.beatType} value={this.state.beatType}
options={[ options={[
{ value: 'filebeat', text: 'Filebeat' }, {
{ value: 'metricbeat', text: 'Metricbeat' }, value: 'filebeat',
text: 'Filebeat',
},
{
value: 'metricbeat',
text: 'Metricbeat',
},
]} ]}
onChange={(e: any) => this.setState({ beatType: e.target.value })} onChange={(e: any) => this.setState({ beatType: e.target.value })}
fullWidth={true} fullWidth={true}
@ -140,7 +162,12 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3>Platform:</h3> <h3>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.platformTitle"
defaultMessage="Platform:"
/>
</h3>
</EuiTitle> </EuiTitle>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
@ -176,8 +203,13 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3> <h3>
On the host where your {capitalize(this.state.beatType)} is installed, <FormattedMessage
run: id="xpack.beatsManagement.enrollBeat.yourBeatTypeHostTitle"
defaultMessage="On the host where your {beatType} is installed, run:"
values={{
beatType: capitalize(this.state.beatType),
}}
/>
</h3> </h3>
</EuiTitle> </EuiTitle>
</EuiFlexItem> </EuiFlexItem>
@ -187,7 +219,14 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
className="euiFieldText euiFieldText--fullWidth" className="euiFieldText euiFieldText--fullWidth"
style={{ textAlign: 'left' }} style={{ textAlign: 'left' }}
> >
$ {this.state.command} enroll {window.location.protocol} <FormattedMessage
id="xpack.beatsManagement.enrollBeat.stateCommandEnrollLocationProtocolTitle"
defaultMessage="$ {stateCommand} enroll {locationProtocol}"
values={{
stateCommand: this.state.command,
locationProtocol: window.location.protocol,
}}
/>
{`//`} {`//`}
{window.location.host} {window.location.host}
{this.props.libs.framework.baseURLPath {this.props.libs.framework.baseURLPath
@ -203,7 +242,15 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiTitle size="xs"> <EuiTitle size="xs">
<h3>Waiting for {capitalize(this.state.beatType)} to enroll...</h3> <h3>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.waitingBeatTypeToEnrollTitle"
defaultMessage="Waiting for {beatType} to enroll…"
values={{
beatType: capitalize(this.state.beatType),
}}
/>
</h3>
</EuiTitle> </EuiTitle>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
@ -218,7 +265,10 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
)} )}
{this.state.enrolledBeat && ( {this.state.enrolledBeat && (
<EuiModalBody> <EuiModalBody>
The Beat is now enrolled in central management: <FormattedMessage
id="xpack.beatsManagement.enrollBeat.beatEnrolledTitle"
defaultMessage="The Beat is now enrolled in central management:"
/>
<br /> <br />
<br /> <br />
<br /> <br />
@ -227,17 +277,32 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
columns={[ columns={[
{ {
field: 'type', field: 'type',
name: 'Beat Type', name: (
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.beatTypeColumnName"
defaultMessage="Beat Type"
/>
),
sortable: false, sortable: false,
}, },
{ {
field: 'version', field: 'version',
name: 'Version', name: (
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.versionColumnName"
defaultMessage="Version"
/>
),
sortable: false, sortable: false,
}, },
{ {
field: 'host_name', field: 'host_name',
name: 'Hostname', name: (
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.hostnameColumnName"
defaultMessage="Hostname"
/>
),
sortable: false, sortable: false,
}, },
]} ]}
@ -276,4 +341,6 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
} }
} }
export const EnrollBeatPage = withUrlState<BeatsProps>(EnrollBeat); export const EnrollBeatPageUi = withUrlState<BeatsProps>(EnrollBeat);
export const EnrollBeatPage = injectI18n(EnrollBeatPageUi);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui'; import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import { BeatTag, CMBeat } from '../../../common/domain_types'; import { BeatTag, CMBeat } from '../../../common/domain_types';
@ -14,6 +15,7 @@ import { FrontendLibs } from '../../lib/lib';
interface PageProps extends URLStateProps<AppURLState>, RouteComponentProps<any> { interface PageProps extends URLStateProps<AppURLState>, RouteComponentProps<any> {
loadBeats: any; loadBeats: any;
libs: FrontendLibs; libs: FrontendLibs;
intl: InjectedIntl;
} }
export class FinishWalkthrough extends React.Component<PageProps, any> { export class FinishWalkthrough extends React.Component<PageProps, any> {
constructor(props: PageProps) { constructor(props: PageProps) {
@ -47,10 +49,22 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
<EuiPageContent> <EuiPageContent>
<EuiEmptyPrompt <EuiEmptyPrompt
iconType="logoBeats" iconType="logoBeats"
title={<h2>Your Beat is enrolled. What's next?</h2>} title={
<h2>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.nextStepTitle"
defaultMessage="Your Beat is enrolled. What's next?"
/>
</h2>
}
body={ body={
<React.Fragment> <React.Fragment>
<p>Start your Beat to check for configuration errors, then click Done.</p> <p>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.nextStepDescription"
defaultMessage="Start your Beat to check for configuration errors, then click Done."
/>
</p>
</React.Fragment> </React.Fragment>
} }
actions={ actions={
@ -61,7 +75,10 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
goTo('/overview/beats'); goTo('/overview/beats');
}} }}
> >
Done <FormattedMessage
id="xpack.beatsManagement.enrollBeat.firstBeatEnrollingDoneButtonLabel"
defaultMessage="Done"
/>
</EuiButton> </EuiButton>
} }
/> />
@ -75,16 +92,32 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
beats.map(({ id }) => ({ beatId: id, tag: tag.id })); beats.map(({ id }) => ({ beatId: id, tag: tag.id }));
private assignTagToBeat = async () => { private assignTagToBeat = async () => {
const { intl } = this.props;
if (!this.props.urlState.enrollmentToken) { if (!this.props.urlState.enrollmentToken) {
return alert('Invalid URL, no enrollmentToken found'); return alert(
intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.assignTagToBeatInvalidURLNoTokenFountTitle',
defaultMessage: 'Invalid URL, no enrollmentToken found',
})
);
} }
if (!this.props.urlState.createdTag) { if (!this.props.urlState.createdTag) {
return alert('Invalid URL, no createdTag found'); return alert(
intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.assignTagToBeatInvalidURLNoTagFoundTitle',
defaultMessage: 'Invalid URL, no createdTag found',
})
);
} }
const beat = await this.props.libs.beats.getBeatWithToken(this.props.urlState.enrollmentToken); const beat = await this.props.libs.beats.getBeatWithToken(this.props.urlState.enrollmentToken);
if (!beat) { if (!beat) {
return alert('Error: Beat not enrolled properly'); return alert(
intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.assignTagToBeatNotEnrolledProperlyTitle',
defaultMessage: 'Error: Beat not enrolled properly',
})
);
} }
const tags = await this.props.libs.tags.getTagsWithIds([this.props.urlState.createdTag]); const tags = await this.props.libs.tags.getTagsWithIds([this.props.urlState.createdTag]);
@ -98,4 +131,6 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
}; };
} }
export const FinishWalkthroughPage = withUrlState<PageProps>(FinishWalkthrough); const FinishWalkthroughPageUi = withUrlState<PageProps>(FinishWalkthrough);
export const FinishWalkthroughPage = injectI18n(FinishWalkthroughPageUi);

View file

@ -11,6 +11,7 @@ import {
EuiTabs, EuiTabs,
} from '@elastic/eui'; } from '@elastic/eui';
import { EuiButton } from '@elastic/eui'; import { EuiButton } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom'; import { Redirect, Route, Switch } from 'react-router-dom';
import { CMPopulatedBeat } from '../../../common/domain_types'; import { CMPopulatedBeat } from '../../../common/domain_types';
@ -32,6 +33,7 @@ import { TagsPage } from './tags';
interface MainPagesProps extends URLStateProps<AppURLState> { interface MainPagesProps extends URLStateProps<AppURLState> {
libs: FrontendLibs; libs: FrontendLibs;
location: any; location: any;
intl: InjectedIntl;
} }
interface MainPagesState { interface MainPagesState {
@ -68,6 +70,7 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
} }
public render() { public render() {
const { intl } = this.props;
if ( if (
this.state.loadedBeatsAtLeastOnce && this.state.loadedBeatsAtLeastOnce &&
this.state.unfilteredBeats.length === 0 && this.state.unfilteredBeats.length === 0 &&
@ -78,7 +81,12 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
const tabs = [ const tabs = [
{ {
id: '/overview/beats', id: '/overview/beats',
name: 'Enrolled Beats', name: (
<FormattedMessage
id="xpack.beatsManagement.beats.enrolledBeatsTabTitle"
defaultMessage="Enrolled Beats"
/>
),
disabled: false, disabled: false,
}, },
// { // {
@ -88,7 +96,12 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
// }, // },
{ {
id: '/overview/tags', id: '/overview/tags',
name: 'Configuration tags', name: (
<FormattedMessage
id="xpack.beatsManagement.beats.configurationTagsTabTitle"
defaultMessage="Configuration tags"
/>
),
disabled: false, disabled: false,
}, },
]; ];
@ -96,19 +109,28 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
const walkthroughSteps = [ const walkthroughSteps = [
{ {
id: '/overview/initial/beats', id: '/overview/initial/beats',
name: 'Enroll Beat', name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.enrollBeatStepLabel',
defaultMessage: 'Enroll Beat',
}),
disabled: false, disabled: false,
page: EnrollBeatPage, page: EnrollBeatPage,
}, },
{ {
id: '/overview/initial/tag', id: '/overview/initial/tag',
name: 'Create tag', name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.createTagStepLabel',
defaultMessage: 'Create tag',
}),
disabled: false, disabled: false,
page: CreateTagPageFragment, page: CreateTagPageFragment,
}, },
{ {
id: '/overview/initial/finish', id: '/overview/initial/finish',
name: 'finish', name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.finishStepLabel',
defaultMessage: 'Finish',
}),
disabled: false, disabled: false,
page: FinishWalkthroughPage, page: FinishWalkthroughPage,
}, },
@ -117,16 +139,27 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
if (this.props.location.pathname === '/overview/initial/help') { if (this.props.location.pathname === '/overview/initial/help') {
return ( return (
<NoDataLayout <NoDataLayout
title="Beats central management (Beta)" title={intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.beatsCentralManagementTitle',
defaultMessage: 'Beats central management (Beta)',
})}
actionSection={ actionSection={
<ConnectedLink path="/overview/initial/beats"> <ConnectedLink path="/overview/initial/beats">
<EuiButton color="primary" fill> <EuiButton color="primary" fill>
Enroll Beat <FormattedMessage
id="xpack.beatsManagement.enrollBeat.enrollBeatButtonLabel"
defaultMessage="Enroll Beat"
/>
</EuiButton> </EuiButton>
</ConnectedLink> </ConnectedLink>
} }
> >
<p>Manage your configurations in a central location.</p> <p>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.beatsCentralManagementDescription"
defaultMessage="Manage your configurations in a central location."
/>
</p>
</NoDataLayout> </NoDataLayout>
); );
} }
@ -134,7 +167,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
if (this.props.location.pathname.includes('/overview/initial')) { if (this.props.location.pathname.includes('/overview/initial')) {
return ( return (
<WalkthroughLayout <WalkthroughLayout
title="Get started with Beats central management" title={intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.getStartedBeatsCentralManagementTitle',
defaultMessage: 'Get started with Beats central management',
})}
walkthroughSteps={walkthroughSteps} walkthroughSteps={walkthroughSteps}
goTo={this.props.goTo} goTo={this.props.goTo}
activePath={this.props.location.pathname} activePath={this.props.location.pathname}
@ -171,7 +207,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
return ( return (
<PrimaryLayout <PrimaryLayout
title="Beats" title={intl.formatMessage({
id: 'xpack.beatsManagement.beatsRouteTitle',
defaultMessage: 'Beats',
})}
actionSection={ actionSection={
<Switch> <Switch>
<Route <Route
@ -192,7 +231,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
<EuiTabs>{renderedTabs}</EuiTabs> <EuiTabs>{renderedTabs}</EuiTabs>
<RouteWithBreadcrumb <RouteWithBreadcrumb
title="Beats List" title={intl.formatMessage({
id: 'xpack.beatsManagement.beatsListRouteTitle',
defaultMessage: 'Beats List',
})}
path="/overview/beats/:action?/:enrollmentToken?" path="/overview/beats/:action?/:enrollmentToken?"
render={(props: any) => ( render={(props: any) => (
<BeatsPage <BeatsPage
@ -205,7 +247,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
)} )}
/> />
<RouteWithBreadcrumb <RouteWithBreadcrumb
title="Activity Overview" title={intl.formatMessage({
id: 'xpack.beatsManagement.activityOverviewRouteTitle',
defaultMessage: 'Activity Overview',
})}
path="/overview/activity" path="/overview/activity"
exact={true} exact={true}
render={(props: any) => ( render={(props: any) => (
@ -213,7 +258,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
)} )}
/> />
<RouteWithBreadcrumb <RouteWithBreadcrumb
title="Tags List" title={intl.formatMessage({
id: 'xpack.beatsManagement.tagsListRouteTitle',
defaultMessage: 'Tags List',
})}
path="/overview/tags" path="/overview/tags"
exact={true} exact={true}
render={(props: any) => <TagsPage {...this.props} libs={this.props.libs} {...props} />} render={(props: any) => <TagsPage {...this.props} libs={this.props.libs} {...props} />}
@ -251,4 +299,6 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
}; };
} }
export const MainPages = withUrlState<MainPagesProps>(MainPagesComponent); const MainPagesUi = withUrlState<MainPagesProps>(MainPagesComponent);
export const MainPages = injectI18n(MainPagesUi);

View file

@ -5,6 +5,7 @@
*/ */
import { EuiButton } from '@elastic/eui'; import { EuiButton } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { BeatTag } from '../../../common/domain_types'; import { BeatTag } from '../../../common/domain_types';
import { AppURLState } from '../../app'; import { AppURLState } from '../../app';
@ -16,6 +17,7 @@ import { FrontendLibs } from '../../lib/lib';
interface TagsPageProps extends URLStateProps<AppURLState> { interface TagsPageProps extends URLStateProps<AppURLState> {
libs: FrontendLibs; libs: FrontendLibs;
intl: InjectedIntl;
} }
interface TagsPageState { interface TagsPageState {
@ -23,7 +25,7 @@ interface TagsPageState {
tableRef: any; tableRef: any;
} }
export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState> { class TagsPageUi extends React.PureComponent<TagsPageProps, TagsPageState> {
public static ActionArea = ({ goTo }: TagsPageProps) => ( public static ActionArea = ({ goTo }: TagsPageProps) => (
<EuiButton <EuiButton
size="s" size="s"
@ -32,7 +34,10 @@ export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState>
goTo('/tag/create'); goTo('/tag/create');
}} }}
> >
Add Tag <FormattedMessage
id="xpack.beatsManagement.tags.addTagButtonLabel"
defaultMessage="Add Tag"
/>
</EuiButton> </EuiButton>
); );
@ -78,13 +83,18 @@ export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState>
} }
private handleTagsAction = async (action: AssignmentActionType, payload: any) => { private handleTagsAction = async (action: AssignmentActionType, payload: any) => {
const { intl } = this.props;
switch (action) { switch (action) {
case AssignmentActionType.Delete: case AssignmentActionType.Delete:
const tags = this.getSelectedTags().map((tag: BeatTag) => tag.id); const tags = this.getSelectedTags().map((tag: BeatTag) => tag.id);
const success = await this.props.libs.tags.delete(tags); const success = await this.props.libs.tags.delete(tags);
if (!success) { if (!success) {
alert( alert(
'Some of these tags might be assigned to beats. Please ensure tags being removed are not activly assigned' intl.formatMessage({
id: 'xpack.beatsManagement.tags.someTagsMightBeAssignedToBeatsTitle',
defaultMessage:
'Some of these tags might be assigned to beats. Please ensure tags being removed are not activly assigned',
})
); );
} else { } else {
this.loadTags(); this.loadTags();
@ -109,3 +119,8 @@ export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState>
}); });
} }
} }
const TagsPageWrapped = injectI18n(TagsPageUi);
export const TagsPage = TagsPageWrapped as typeof TagsPageWrapped & {
ActionArea: typeof TagsPageUi['ActionArea'];
};

View file

@ -3,14 +3,26 @@
* or more contributor license agreements. Licensed under the Elastic License; * or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import * as React from 'react'; import * as React from 'react';
import { NoDataLayout } from '../components/layouts/no_data'; import { NoDataLayout } from '../components/layouts/no_data';
export const NoAccessPage: React.SFC<any> = () => ( export const NoAccessPage = injectI18n(({ intl }) => (
<NoDataLayout title="Access denied" actionSection={[]}> <NoDataLayout
title={intl.formatMessage({
id: 'xpack.beatsManagement.noAccess.accessDeniedTitle',
defaultMessage: 'Access denied',
})}
actionSection={[]}
>
<p> <p>
You are not authorized to access Beats central management. To use Beats central management, <FormattedMessage
you need the privileges granted by the `beats_admin` role. id="xpack.beatsManagement.noAccess.accessDeniedDescription"
defaultMessage="You are not authorized to access Beats central management. To use Beats central management,
you need the privileges granted by the {beatsAdminRole} role."
values={{ beatsAdminRole: '`beats_admin`' }}
/>
</p> </p>
</NoDataLayout> </NoDataLayout>
); ));

View file

@ -9,6 +9,7 @@ import 'brace/theme/github';
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { sample } from 'lodash'; import { sample } from 'lodash';
import React from 'react'; import React from 'react';
import { UNIQUENESS_ENFORCING_TYPES } from 'x-pack/plugins/beats_management/common/constants'; import { UNIQUENESS_ENFORCING_TYPES } from 'x-pack/plugins/beats_management/common/constants';
@ -21,6 +22,7 @@ import { FrontendLibs } from '../../lib/lib';
interface TagPageProps extends URLStateProps<AppURLState> { interface TagPageProps extends URLStateProps<AppURLState> {
libs: FrontendLibs; libs: FrontendLibs;
match: any; match: any;
intl: InjectedIntl;
} }
interface TagPageState { interface TagPageState {
@ -56,9 +58,26 @@ export class TagPageComponent extends React.PureComponent<TagPageProps, TagPageS
} }
} }
public render() { public render() {
const { intl } = this.props;
return ( return (
<PrimaryLayout <PrimaryLayout
title={this.mode === 'create' ? 'Create Tag' : `Update Tag: ${this.state.tag.id}`} title={
this.mode === 'create'
? intl.formatMessage({
id: 'xpack.beatsManagement.tag.createTagTitle',
defaultMessage: 'Create Tag',
})
: intl.formatMessage(
{
id: 'xpack.beatsManagement.tag.updateTagTitle',
defaultMessage: 'Update Tag: {tagId}',
},
{
tagId: this.state.tag.id,
}
)
}
> >
<div> <div>
<TagEdit <TagEdit
@ -91,12 +110,18 @@ export class TagPageComponent extends React.PureComponent<TagPageProps, TagPageS
} }
onClick={this.saveTag} onClick={this.saveTag}
> >
Save <FormattedMessage
id="xpack.beatsManagement.tag.saveButtonLabel"
defaultMessage="Save"
/>
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={() => this.props.goTo('/overview/tags')}> <EuiButtonEmpty onClick={() => this.props.goTo('/overview/tags')}>
Cancel <FormattedMessage
id="xpack.beatsManagement.tag.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty> </EuiButtonEmpty>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
@ -141,4 +166,6 @@ export class TagPageComponent extends React.PureComponent<TagPageProps, TagPageS
.map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some(uniqueType => uniqueType === type)) .map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some(uniqueType => uniqueType === type))
.reduce((acc, cur) => (cur ? acc + 1 : acc), 0); .reduce((acc, cur) => (cur ? acc + 1 : acc), 0);
} }
export const TagPage = withUrlState<TagPageProps>(TagPageComponent); export const TagPageUi = withUrlState<TagPageProps>(TagPageComponent);
export const TagPage = injectI18n(TagPageUi);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { i18n } from '@kbn/i18n';
import React from 'react'; import React from 'react';
import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; import { HashRouter, Redirect, Route, Switch } from 'react-router-dom';
import { Header } from './components/layouts/header'; import { Header } from './components/layouts/header';
@ -26,11 +27,15 @@ export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => {
breadcrumbs={[ breadcrumbs={[
{ {
href: '#/management', href: '#/management',
text: 'Management', text: i18n.translate('xpack.beatsManagement.router.managementTitle', {
defaultMessage: 'Management',
}),
}, },
{ {
href: '#/management/beats_management', href: '#/management/beats_management',
text: 'Beats', text: i18n.translate('xpack.beatsManagement.router.beatsTitle', {
defaultMessage: 'Beats',
}),
}, },
...breadcrumbs, ...breadcrumbs,
]} ]}
@ -55,11 +60,16 @@ export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => {
<Route path="/overview" render={(props: any) => <MainPages {...props} libs={libs} />} /> <Route path="/overview" render={(props: any) => <MainPages {...props} libs={libs} />} />
<RouteWithBreadcrumb <RouteWithBreadcrumb
title={params => { title={params => {
return `Beats: ${params.beatId}`; return i18n.translate('xpack.beatsManagement.router.beatTitle', {
defaultMessage: 'Beats: {beatId}',
values: { beatId: params.beatId },
});
}} }}
parentBreadcrumbs={[ parentBreadcrumbs={[
{ {
text: 'Beats List', text: i18n.translate('xpack.beatsManagement.router.beatsListTitle', {
defaultMessage: 'Beats List',
}),
href: '#/management/beats_management/overview/beats', href: '#/management/beats_management/overview/beats',
}, },
]} ]}
@ -69,13 +79,20 @@ export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => {
<RouteWithBreadcrumb <RouteWithBreadcrumb
title={params => { title={params => {
if (params.action === 'create') { if (params.action === 'create') {
return 'Create Tag'; return i18n.translate('xpack.beatsManagement.router.createTagTitle', {
defaultMessage: 'Create Tag',
});
} }
return `Tag: ${params.tagid}`; return i18n.translate('xpack.beatsManagement.router.tagTitle', {
defaultMessage: 'Tag: {tagId}',
values: { tagId: params.tagid },
});
}} }}
parentBreadcrumbs={[ parentBreadcrumbs={[
{ {
text: 'Tags List', text: i18n.translate('xpack.beatsManagement.router.tagsListTitle', {
defaultMessage: 'Tags List',
}),
href: '#/management/beats_management/overview/tags', href: '#/management/beats_management/overview/tags',
}, },
]} ]}