Beats Management translations (#25228)

* 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 11:21:25 +03:00 committed by GitHub
parent 64e26c4a3f
commit 64081cdcc7
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",
"regionMap": "src/core_plugins/region_map",
"statusPage": "src/core_plugins/status_page",
"tagCloud": "src/core_plugins/tagcloud",
"tagCloud": "src/core_plugins/tagcloud",
"tileMap": "src/core_plugins/tile_map",
"timelion": "src/core_plugins/timelion",
"tsvb": "src/core_plugins/metrics",
"tagCloud": "src/core_plugins/tagcloud",
"tsvb": "src/core_plugins/metrics",
"xpack.beatsManagement": "x-pack/plugins/beats_management",
"xpack.graph": "x-pack/plugins/graph",
"xpack.grokDebugger": "x-pack/plugins/grokdebugger",
"xpack.idxMgmt": "x-pack/plugins/index_management",

View file

@ -6,25 +6,30 @@
// @ts-ignore
import { EuiBasicTable, EuiLink } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { ConfigurationBlock } from '../../common/domain_types';
import { supportedConfigs } from '../config_schemas';
import { getSupportedConfig } from '../config_schemas_translations_map';
interface ComponentProps {
configs: ConfigurationBlock[];
onConfigClick: (action: 'edit' | 'delete', config: ConfigurationBlock) => any;
intl: InjectedIntl;
}
export const ConfigList: React.SFC<ComponentProps> = props => (
const ConfigListUi: React.SFC<ComponentProps> = props => (
<EuiBasicTable
items={props.configs || []}
columns={[
{
field: 'type',
name: 'Type',
name: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.typeColumnName',
defaultMessage: 'Type',
}),
truncateText: false,
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 (
<EuiLink onClick={() => props.onConfigClick('edit', config)}>
@ -35,22 +40,43 @@ export const ConfigList: React.SFC<ComponentProps> = props => (
},
{
field: 'module',
name: 'Module',
name: props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.moduleColumnName',
defaultMessage: 'Module',
}),
truncateText: false,
render: (value: string) => {
return value || 'N/A';
return (
value ||
props.intl.formatMessage({
id: 'xpack.beatsManagement.tagTable.moduleColumn.notAvailibaleLabel',
defaultMessage: 'N/A',
})
);
},
},
{
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: [
{
name: 'Remove',
description: 'Remove this config from tag',
name: props.intl.formatMessage({
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',
icon: 'trash',
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 { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { ActionDefinition } from './table_type_configs';
interface ActionButtonProps {
itemName: 'Beats' | 'Tags';
actions: ActionDefinition[];
intl: InjectedIntl;
isPopoverVisible: boolean;
actionHandler(action: string, payload?: any): void;
hidePopover(): void;
showPopover(): void;
}
export function ActionButton(props: ActionButtonProps) {
const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover } = props;
export const ActionButton = injectI18n((props: ActionButtonProps) => {
const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover, intl } = props;
if (actions.length === 0) {
return null;
}
@ -27,7 +29,10 @@ export function ActionButton(props: ActionButtonProps) {
anchorPosition="downLeft"
button={
<EuiButton iconSide="right" iconType="arrowDown" onClick={showPopover}>
Bulk Action
<FormattedMessage
id="xpack.beatsManagement.table.bulkActionButtonLabel"
defaultMessage="Bulk Action"
/>
</EuiButton>
}
closePopover={hidePopover}
@ -41,7 +46,13 @@ export function ActionButton(props: ActionButtonProps) {
panels={[
{
id: 0,
title: `Manage ${props.itemName}`,
title: intl.formatMessage(
{
id: 'xpack.beatsManagement.table.bulkActionMenuLabel',
defaultMessage: 'Manage {itemName}',
},
{ itemName: props.itemName }
),
items: actions.map(action => ({
...action,
onClick: () => actionHandler(action.action),
@ -51,4 +62,4 @@ export function ActionButton(props: ActionButtonProps) {
/>
</EuiPopover>
);
}
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,6 +16,7 @@ import {
EuiToolTipProps,
} from '@elastic/eui';
import { EuiIcon } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { isArray } from 'lodash';
import React from 'react';
import { AssignmentControlSchema } from '../table';
@ -24,6 +25,7 @@ import { ActionControl } from './action_control';
import { TagBadgeList } from './tag_badge_list';
interface ComponentProps {
intl: InjectedIntl;
itemType: string;
items?: any[];
schema: AssignmentControlSchema[];
@ -40,7 +42,7 @@ interface FixedEuiToolTipProps extends EuiToolTipProps {
}
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) {
super(props);
@ -53,7 +55,7 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
schemaOrArray: AssignmentControlSchema | AssignmentControlSchema[],
panels: any = []
) {
const { items, actionHandler } = this.props;
const { items, actionHandler, intl } = this.props;
let schema: AssignmentControlSchema | null = null;
let schemaArray: AssignmentControlSchema[] | null = null;
@ -91,14 +93,23 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
});
} else {
if (items === undefined) {
panel.content = 'Unknown Error.';
panel.content = intl.formatMessage({
id: 'xpack.beatsManagement.tableControls.unknownErrorMessage',
defaultMessage: 'Unknown Error.',
});
} else if (items.length === 0) {
panel.content = (
<EuiPanel>
<EuiCard
icon={<EuiIcon size="l" type="bolt" />}
title="No tags found."
description="Please create a new configuration tag."
title={intl.formatMessage({
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>
);
@ -121,7 +132,7 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
}
public render() {
const { itemType, selectionCount, schema } = this.props;
const { itemType, selectionCount, schema, intl } = this.props;
return (
<EuiPopover
@ -131,8 +142,21 @@ export class OptionControl extends React.PureComponent<ComponentProps, Component
delay="long"
content={
selectionCount === 0
? `Select ${itemType} to perform operations such as setting tags and unenrolling Beats.`
: `Manage your selected ${itemType}`
? intl.formatMessage(
{
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
@ -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>
</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.
*/
// @ts-ignore
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import Formsy, { addValidationRule, FieldValue, FormData } from 'formsy-react';
import yaml from 'js-yaml';
import { get } from 'lodash';
@ -55,6 +56,7 @@ addValidationRule('isYaml', (values: FormData, value: FieldValue) => {
});
interface ComponentProps {
intl: InjectedIntl;
values: ConfigurationBlock;
schema: YamlConfigSchema[];
id: string;
@ -62,7 +64,7 @@ interface ComponentProps {
canSubmit(canIt: boolean): any;
}
export class ConfigForm extends React.Component<ComponentProps, any> {
class ConfigFormUi extends React.Component<ComponentProps, any> {
private form = React.createRef<HTMLButtonElement>();
constructor(props: ComponentProps) {
super(props);
@ -97,6 +99,7 @@ export class ConfigForm extends React.Component<ComponentProps, any> {
this.props.onSubmit(model);
};
public render() {
const { intl } = this.props;
return (
<div>
<br />
@ -181,9 +184,15 @@ export class ConfigForm extends React.Component<ComponentProps, any> {
)}
helpText={schema.ui.helpText}
label={schema.ui.label}
options={[{ value: '', text: 'Please Select An Option' }].concat(
schema.options || []
)}
options={[
{
value: '',
text: intl.formatMessage({
id: 'xpack.beatsManagement.tagConfig.selectOptionLabel',
defaultMessage: 'Please Select An Option',
}),
},
].concat(schema.options || [])}
validations={schema.validations}
validationError={schema.error}
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,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { ConfigurationBlock } from '../../../../common/domain_types';
import { supportedConfigs } from '../../../config_schemas';
import { getSupportedConfig } from '../../../config_schemas_translations_map';
import { ConfigForm } from './config_form';
interface ComponentProps {
intl: InjectedIntl;
configBlock?: ConfigurationBlock;
onClose(): 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 editMode: boolean;
private supportedConfigs = getSupportedConfig();
constructor(props: any) {
super(props);
this.editMode = props.configBlock !== undefined;
@ -48,7 +51,7 @@ export class ConfigView extends React.Component<ComponentProps, any> {
this.state = {
valid: false,
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() {
const { intl } = this.props;
return (
<EuiFlyout onClose={this.props.onClose}>
<EuiFlyoutHeader>
<EuiTitle size="m">
<h2>
{this.editMode
? this.props.onSave
? 'Edit configuration block'
: 'View configuration block'
: 'Add configuration block'}
{this.editMode ? (
this.props.onSave ? (
<FormattedMessage
id="xpack.beatsManagement.tagConfig.editConfigurationTitle"
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>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiFormRow label="Type">
<EuiFormRow
label={
<FormattedMessage
id="xpack.beatsManagement.tagConfig.typeLabel"
defaultMessage="Type"
/>
}
>
<EuiSelect
options={supportedConfigs}
options={this.supportedConfigs}
value={this.state.configBlock.type}
disabled={this.editMode}
onChange={this.onValueChange('type')}
/>
</EuiFormRow>
<EuiFormRow label="Description">
<EuiFormRow
label={
<FormattedMessage
id="xpack.beatsManagement.tagConfig.descriptionLabel"
defaultMessage="Description"
/>
}
>
<EuiFieldText
value={this.state.configBlock.description}
disabled={!this.props.onSave}
onChange={this.onValueChange('description')}
placeholder="Description (optional)"
placeholder={intl.formatMessage({
id: 'xpack.beatsManagement.tagConfig.descriptionPlaceholder',
defaultMessage: 'Description (optional)',
})}
/>
</EuiFormRow>
<h3>
{
(supportedConfigs.find(config => this.state.configBlock.type === config.value) as any)
.text
}
&nbsp;configuration
<FormattedMessage
id="xpack.beatsManagement.tagConfig.configurationTypeText"
defaultMessage="{configType} configuration"
values={{
configType: (this.supportedConfigs.find(
config => this.state.configBlock.type === config.value
) as any).text,
}}
/>
</h3>
<EuiHorizontalRule />
@ -119,12 +157,14 @@ export class ConfigView extends React.Component<ComponentProps, any> {
ref={this.form}
values={this.state.configBlock}
id={
(supportedConfigs.find(config => this.state.configBlock.type === config.value) as any)
.value
(this.supportedConfigs.find(
config => this.state.configBlock.type === config.value
) as any).value
}
schema={
(supportedConfigs.find(config => this.state.configBlock.type === config.value) as any)
.config
(this.supportedConfigs.find(
config => this.state.configBlock.type === config.value
) as any).config
}
/>
</EuiFlyoutBody>
@ -132,7 +172,10 @@ export class ConfigView extends React.Component<ComponentProps, any> {
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={this.props.onClose}>
Close
<FormattedMessage
id="xpack.beatsManagement.tagConfig.closeButtonLabel"
defaultMessage="Close"
/>
</EuiButtonEmpty>
</EuiFlexItem>
{this.props.onSave && (
@ -141,12 +184,15 @@ export class ConfigView extends React.Component<ComponentProps, any> {
disabled={!this.state.valid}
fill
onClick={() => {
if (this.form.current) {
this.form.current.submit();
if (this.form.current && this.form.current.getWrappedInstance()) {
this.form.current.getWrappedInstance().submit();
}
}}
>
Save
<FormattedMessage
id="xpack.beatsManagement.tagConfig.saveButtonLabel"
defaultMessage="Save"
/>
</EuiButton>
</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,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import 'brace/mode/yaml';
import 'brace/theme/github';
import { isEqual } from 'lodash';
@ -37,6 +38,7 @@ interface TagEditProps {
onDetachBeat: (beatIds: string[]) => void;
onTagChange: (field: keyof BeatTag, value: string) => any;
attachedBeats: CMBeat[] | null;
intl: InjectedIntl;
}
interface TagEditState {
@ -45,7 +47,7 @@ interface TagEditState {
selectedConfigIndex?: number;
}
export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
class TagEditUi extends React.PureComponent<TagEditProps, TagEditState> {
constructor(props: TagEditProps) {
super(props);
@ -56,17 +58,25 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
}
public render() {
const { tag, attachedBeats } = this.props;
const { tag, attachedBeats, intl } = this.props;
return (
<div>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xs">
<h3>Tag details</h3>
<h3>
<FormattedMessage
id="xpack.beatsManagement.tag.tagDetailsTitle"
defaultMessage="Tag details"
/>
</h3>
</EuiTitle>
<EuiText color="subdued">
<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>
</EuiText>
<div>
@ -76,7 +86,12 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
<EuiFlexItem>
<EuiForm>
<EuiFormRow
label="Tag Name"
label={
<FormattedMessage
id="xpack.beatsManagement.tag.tagNameLabel"
defaultMessage="Tag Name"
/>
}
isInvalid={!!this.getNameError(tag.id)}
error={this.getNameError(tag.id) || undefined}
>
@ -86,11 +101,19 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
onChange={this.updateTag('id')}
disabled={this.props.mode === 'edit'}
value={tag.id}
placeholder="Tag name (required)"
placeholder={intl.formatMessage({
id: 'xpack.beatsManagement.tag.tagNamePlaceholder',
defaultMessage: 'Tag name (required)',
})}
/>
</EuiFormRow>
{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')} />
</EuiFormRow>
)}
@ -107,13 +130,20 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
>
<EuiFlexItem>
<EuiTitle size="xs">
<h3>Configuration blocks</h3>
<h3>
<FormattedMessage
id="xpack.beatsManagement.tag.tagConfigurationsTitle"
defaultMessage="Configuration blocks"
/>
</h3>
</EuiTitle>
<EuiText color="subdued">
<p>
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.
<FormattedMessage
id="xpack.beatsManagement.tag.tagConfigurationsDescription"
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>
</EuiText>
</EuiFlexItem>
@ -143,7 +173,10 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
this.setState({ showFlyout: true });
}}
>
Add configuration block
<FormattedMessage
id="xpack.beatsManagement.tag.addConfigurationButtonLabel"
defaultMessage="Add configuration block"
/>
</EuiButton>
</div>
</EuiFlexItem>
@ -154,7 +187,12 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
<EuiHorizontalRule />
<EuiTitle size="xs">
<h3>Beats with this tag</h3>
<h3>
<FormattedMessage
id="xpack.beatsManagement.tag.beatsAssignedToTagTitle"
defaultMessage="Beats with this tag"
/>
</h3>
</EuiTitle>
<Table
assignmentOptions={{
@ -198,8 +236,12 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
}
private getNameError = (name: string) => {
const { intl } = this.props;
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 {
return false;
}
@ -219,3 +261,5 @@ export class TagEdit extends React.PureComponent<TagEditProps, TagEditState> {
? this.props.onTagChange(key, value)
: (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',
ui: {
label: 'Paths',
label: 'filebeatInputConfig.paths.ui.label',
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`,
},
validations: 'isPaths',
error: 'One file path per line',
error: 'filebeatInputConfig.paths.error',
required: true,
},
{
id: 'other',
ui: {
label: 'Other Config',
label: 'filebeatInputConfig.other.ui.label',
type: 'code',
helpText: 'Use YAML format to specify other settings for the Filebeat Input',
helpText: 'filebeatInputConfig.other.ui.helpText',
},
validations: 'isYaml',
error: 'Use valid YAML format',
error: 'filebeatInputConfig.other.error',
},
];
@ -35,7 +35,7 @@ const filebeatModuleConfig: YamlConfigSchema[] = [
{
id: 'module',
ui: {
label: 'Module',
label: 'filebeatModuleConfig.module.ui.label',
type: 'select',
},
options: [
@ -108,18 +108,18 @@ const filebeatModuleConfig: YamlConfigSchema[] = [
text: 'traefik',
},
],
error: 'Please select a module',
error: 'filebeatModuleConfig.module.error',
required: true,
},
{
id: 'other',
ui: {
label: 'Other Config',
label: 'filebeatModuleConfig.other.ui.label',
type: 'code',
helpText: 'Use YAML format to specify other settings for the Filebeat Module',
helpText: 'filebeatModuleConfig.other.ui.helpText',
},
validations: 'isYaml',
error: 'Use valid YAML format',
error: 'filebeatModuleConfig.other.error',
},
];
@ -127,7 +127,7 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [
{
id: 'module',
ui: {
label: 'Module',
label: 'metricbeatModuleConfig.module.ui.label',
type: 'select',
},
options: [
@ -272,41 +272,41 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [
text: 'zookeeper',
},
],
error: 'Please select a module',
error: 'metricbeatModuleConfig.module.error',
required: true,
},
{
id: 'hosts',
ui: {
label: 'Hosts',
label: 'metricbeatModuleConfig.hosts.ui.label',
type: 'multi-input',
helpText: 'Put each of the paths on a seperate line',
helpText: 'metricbeatModuleConfig.hosts.ui.helpText',
placeholder: `somehost.local otherhost.local`,
},
validations: 'isHosts',
error: 'One file host per line',
error: 'metricbeatModuleConfig.hosts.error',
required: false,
},
{
id: 'period',
ui: {
label: 'Period',
label: 'metricbeatModuleConfig.period.ui.label',
type: 'input',
},
defaultValue: '10s',
validations: 'isPeriod',
error: 'Invalid Period, must be formatted as `10s` for 10 seconds',
error: 'metricbeatModuleConfig.period.error',
required: true,
},
{
id: 'other',
ui: {
label: 'Other Config',
label: 'metricbeatModuleConfig.other.ui.label',
type: 'code',
helpText: 'Use YAML format to specify other settings for the Metricbeat Module',
helpText: 'metricbeatModuleConfig.other.ui.helpText',
},
validations: 'isYaml',
error: 'Use valid YAML format',
error: 'metricbeatModuleConfig.other.error',
},
];
@ -314,7 +314,7 @@ const outputConfig: YamlConfigSchema[] = [
{
id: 'output',
ui: {
label: 'Output Type',
label: 'outputConfig.output.ui.label',
type: 'select',
},
options: [
@ -335,42 +335,54 @@ const outputConfig: YamlConfigSchema[] = [
text: 'Console',
},
],
error: 'Please select an output type',
error: 'outputConfig.output.error',
required: true,
},
{
id: '{{output}}.hosts',
ui: {
label: 'Hosts',
label: 'outputConfig.hosts.ui.label',
type: 'multi-input',
},
validations: 'isHosts',
error: 'One file host per line',
error: 'outputConfig.hosts.error',
parseValidResult: v => v.split('\n'),
},
{
id: '{{output}}.username',
ui: {
label: 'Username',
label: 'outputConfig.username.ui.label',
type: 'input',
},
validations: 'isString',
error: 'Unprocessable username',
error: 'outputConfig.username.error',
},
{
id: '{{output}}.password',
ui: {
label: 'Password',
label: 'outputConfig.password.ui.label',
type: 'password',
},
validations: 'isString',
error: 'Unprocessable password',
error: 'outputConfig.password.error',
},
];
export const supportedConfigs = [
{ text: 'Filebeat Input', value: 'filebeat.inputs', config: filebeatInputConfig },
{ text: 'Filebeat Module', value: 'filebeat.modules', config: filebeatModuleConfig },
{ text: 'Metricbeat Module', value: 'metricbeat.modules', config: metricbeatModuleConfig },
{ text: 'Output', value: 'output', config: outputConfig },
{
text: 'supportedConfigs.filebeatInput.text',
value: 'filebeat.inputs',
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 { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BASE_PATH } from '../common/constants';
@ -14,13 +16,21 @@ import { FrontendLibs } from './lib/lib';
import { PageRouter } from './router';
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(
<ThemeProvider theme={{ eui: euiVars }}>
<BreadcrumbProvider>
<PageRouter libs={libs} />
</BreadcrumbProvider>
</ThemeProvider>
<I18nProvider>
<ThemeProvider theme={{ eui: euiVars }}>
<BreadcrumbProvider>
<PageRouter libs={libs} />
</BreadcrumbProvider>
</ThemeProvider>
</I18nProvider>
);
}

View file

@ -8,6 +8,7 @@ import { IModule, IScope } from 'angular';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import {
BufferedKibanaServiceCall,
FrameworkAdapter,
@ -103,7 +104,9 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
if (this.hasValidLicense()) {
const registerSection = () =>
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',
order: 30,
});

View file

@ -18,7 +18,7 @@ import { uiModules } from 'ui/modules';
import routes from 'ui/routes';
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 { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest';
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 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 beats = new BeatsLib(new RestBeatsAdapter(api), {
tags,

View file

@ -21,7 +21,7 @@ import { BeatsLib } from '../beats';
import { FrontendDomainLibs, FrontendLibs } from '../lib';
import { AutocompleteSuggestion } from 'ui/autocomplete_providers';
import { supportedConfigs } from '../../config_schemas';
import { getSupportedConfig } from '../../config_schemas_translations_map';
import { TagsLib } from '../tags';
import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory';
import { ElasticsearchLib } from './../elasticsearch';
@ -36,7 +36,7 @@ export function compose(
mockKueryToEsQuery,
suggestions
);
const tags = new TagsLib(new MemoryTagsAdapter([]), supportedConfigs);
const tags = new TagsLib(new MemoryTagsAdapter([]), getSupportedConfig());
const tokens = new MemoryTokensAdapter();
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.
*/
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
export class NotFoundPage extends React.PureComponent {
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 { FormattedMessage } from '@kbn/i18n/react';
import { first, sortByOrder } from 'lodash';
import moment from 'moment';
import React from 'react';
@ -20,14 +21,20 @@ export const BeatDetailsActionSection = ({ beat }: BeatDetailsActionSectionProps
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiText size="xs">
Type:&nbsp;
<strong>{beat.type}</strong>.
<FormattedMessage
id="xpack.beatsManagement.beat.actionSectionTypeLabel"
defaultMessage="Type: {beatType}."
values={{ beatType: <strong>{beat.type}</strong> }}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs">
Version:&nbsp;
<strong>{beat.version}</strong>.
<FormattedMessage
id="xpack.beatsManagement.beat.actionSectionVersionLabel"
defaultMessage="Version: {beatVersion}."
values={{ beatVersion: <strong>{beat.version}</strong> }}
/>
</EuiText>
</EuiFlexItem>
{/* 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 && (
<EuiFlexItem grow={false}>
<EuiText size="xs">
Last Config Update:{' '}
<strong>
{moment(
first(sortByOrder(beat.full_tags, 'last_updated')).last_updated
).fromNow()}
</strong>
.
<FormattedMessage
id="xpack.beatsManagement.beat.lastConfigUpdateMessage"
defaultMessage="Last Config Update: {lastUpdateTime}."
values={{
lastUpdateTime: (
<strong>
{moment(
first(sortByOrder(beat.full_tags, 'last_updated')).last_updated
).fromNow()}
</strong>
),
}}
/>
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
) : (
<div>Beat not found</div>
<div>
<FormattedMessage
id="xpack.beatsManagement.beat.beatNotFoundMessage"
defaultMessage="Beat not found"
/>
</div>
)}
</div>
);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { FrontendLibs } from '../../lib/lib';
@ -11,4 +12,11 @@ interface BeatActivityPageProps {
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,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { flatten, get } from 'lodash';
import React from 'react';
import { TABLE_CONFIG } from '../../../common/constants';
@ -20,17 +21,18 @@ import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/do
import { ConnectedLink } from '../../components/connected_link';
import { TagBadge } from '../../components/tag';
import { ConfigView } from '../../components/tag/config_view/index';
import { supportedConfigs } from '../../config_schemas';
import { getSupportedConfig } from '../../config_schemas_translations_map';
interface PageProps {
beat: CMPopulatedBeat | undefined;
intl: InjectedIntl;
}
interface PageState {
selectedConfig: ConfigurationBlock | null;
}
export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
class BeatDetailPageUi extends React.PureComponent<PageProps, PageState> {
constructor(props: PageProps) {
super(props);
@ -40,9 +42,16 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
}
public render() {
const props = this.props;
const { beat } = props;
const { beat, intl } = props;
if (!beat) {
return <div>Beat not found</div>;
return (
<div>
<FormattedMessage
id="xpack.beatsManagement.beat.beatNotFoundErrorTitle"
defaultMessage="Beat not found"
/>
</div>
);
}
const configurationBlocks = flatten(
beat.full_tags.map((tag: BeatTag) => {
@ -54,7 +63,7 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
...beat,
...configuration,
displayValue: get(
supportedConfigs.find(config => config.value === configuration.type),
getSupportedConfig().find(config => config.value === configuration.type),
'text',
null
),
@ -65,7 +74,10 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
const columns = [
{
field: 'displayValue',
name: 'Type',
name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.typeColumnName',
defaultMessage: 'Type',
}),
sortable: true,
render: (value: string | null, configuration: any) => (
<EuiLink
@ -81,17 +93,26 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
},
{
field: 'module',
name: 'Module',
name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.moduleColumnName',
defaultMessage: 'Module',
}),
sortable: true,
},
{
field: 'description',
name: 'Description',
name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.descriptionColumnName',
defaultMessage: 'Description',
}),
sortable: true,
},
{
field: 'tagId',
name: 'Tag',
name: intl.formatMessage({
id: 'xpack.beatsManagement.beatConfigurations.tagColumnName',
defaultMessage: 'Tag',
}),
render: (id: string, block: any) => (
<ConnectedLink path={`/tag/edit/${id}`}>
<TagBadge
@ -108,13 +129,21 @@ export class BeatDetailPage extends React.PureComponent<PageProps, PageState> {
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xs">
<h4>Configurations</h4>
<h4>
<FormattedMessage
id="xpack.beatsManagement.beat.detailsConfigurationTitle"
defaultMessage="Configurations"
/>
</h4>
</EuiTitle>
<EuiText size="s">
<p>
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.
<FormattedMessage
id="xpack.beatsManagement.beat.detailsConfigurationDescription"
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>
</EuiText>
</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
EuiTabs,
} from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { CMPopulatedBeat } from '../../../common/domain_types';
@ -32,6 +33,7 @@ interface BeatDetailsPageProps extends URLStateProps<AppURLState> {
history: any;
libs: FrontendLibs;
match: Match;
intl: InjectedIntl;
}
interface BeatDetailsPageState {
@ -63,6 +65,7 @@ class BeatDetailsPageComponent extends React.PureComponent<
};
public render() {
const { intl } = this.props;
const { beat } = this.state;
let id;
let name;
@ -72,13 +75,33 @@ class BeatDetailsPageComponent extends React.PureComponent<
name = beat.name;
}
const title = this.state.isLoading
? 'Loading'
: `Beat: ${name || 'No name receved from beat'} (id: ${id})`;
? intl.formatMessage({
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 = [
{
id: `/beat/${id}`,
name: 'Config',
name: intl.formatMessage({
id: 'xpack.beatsManagement.beat.configTabLabel',
defaultMessage: 'Config',
}),
disabled: false,
},
// {
@ -88,7 +111,10 @@ class BeatDetailsPageComponent extends React.PureComponent<
// },
{
id: `/beat/${id}/tags`,
name: 'Configuration Tags',
name: intl.formatMessage({
id: 'xpack.beatsManagement.beat.configurationTagsTabLabel',
defaultMessage: 'Configuration Tags',
}),
disabled: false,
},
];
@ -141,12 +167,18 @@ class BeatDetailsPageComponent extends React.PureComponent<
}
private async loadBeat() {
const { intl } = this.props;
const { beatId } = this.props.match.params;
let beat;
try {
beat = await this.props.libs.beats.get(beatId);
if (!beat) {
throw new Error('beat not found');
throw new Error(
intl.formatMessage({
id: 'xpack.beatsManagement.beat.beatNotFoundErrorMessage',
defaultMessage: 'beat not found',
})
);
}
} catch (e) {
throw new Error(e);
@ -154,4 +186,6 @@ class BeatDetailsPageComponent extends React.PureComponent<
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;
* 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 { NoDataLayout } from '../components/layouts/no_data';
export const EnforceSecurityPage: React.SFC<any> = () => (
<NoDataLayout title="Security is not enabled" actionSection={[]}>
<p>You must enable security in Kibana and Elasticsearch to use Beats central management.</p>
export const EnforceSecurityPage = injectI18n(({ intl }) => (
<NoDataLayout
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>
);
));

View file

@ -3,14 +3,25 @@
* or more contributor license agreements. Licensed under 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 { NoDataLayout } from '../components/layouts/no_data';
export const InvalidLicensePage: React.SFC<any> = () => (
<NoDataLayout title="Expired license" actionSection={[]}>
export const InvalidLicensePage = injectI18n(({ intl }) => (
<NoDataLayout
title={intl.formatMessage({
id: 'xpack.beatsManagement.invalidLicenseTitle',
defaultMessage: 'Expired license',
})}
actionSection={[]}
>
<p>
Your current license is expired. Enrolled Beats will continue to work, but you need a valid
license to access the Beats Management UI.
<FormattedMessage
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>
</NoDataLayout>
);
));

View file

@ -4,10 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
export class ActivityPage extends React.PureComponent {
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,
EuiOverlayMask,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { flatten, intersection, sortBy } from 'lodash';
import moment from 'moment';
import React from 'react';
@ -31,6 +32,7 @@ import { FrontendLibs } from '../../lib/lib';
import { EnrollBeatPage } from './enroll_fragment';
interface BeatsPageProps extends URLStateProps<AppURLState> {
intl: InjectedIntl;
libs: FrontendLibs;
location: any;
beats: CMPopulatedBeat[];
@ -47,7 +49,7 @@ interface ActionAreaProps extends URLStateProps<AppURLState>, RouteComponentProp
libs: FrontendLibs;
}
export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageState> {
class BeatsPageUi extends React.PureComponent<BeatsPageProps, BeatsPageState> {
public static ActionArea = (props: ActionAreaProps) => (
<React.Fragment>
<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>
<EuiButton
size="s"
@ -69,7 +74,10 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
props.goTo(`/overview/beats/enroll`);
}}
>
Enroll Beats
<FormattedMessage
id="xpack.beatsManagement.beats.enrollBeatsButtonLabel"
defaultMessage="Enroll Beats"
/>
</EuiButton>
{props.location.pathname === '/overview/beats/enroll' && (
@ -81,7 +89,12 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
style={{ width: '640px' }}
>
<EuiModalHeader>
<EuiModalHeaderTitle>Enroll a new Beat</EuiModalHeaderTitle>
<EuiModalHeaderTitle>
<FormattedMessage
id="xpack.beatsManagement.beats.enrollNewBeatsTitle"
defaultMessage="Enroll a new Beat"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EnrollBeatPage {...props} />
@ -217,13 +230,38 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
};
private notifyBeatDisenrolled = async (beats: CMPopulatedBeat[]) => {
const { intl } = this.props;
let title;
let text;
if (beats.length === 1) {
title = `"${beats[0].name || beats[0].id}" disenrolled`;
text = `Beat with ID "${beats[0].id}" was disenrolled.`;
title = intl.formatMessage(
{
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 {
title = `${beats.length} beats disenrolled`;
title = intl.formatMessage(
{
id: 'xpack.beatsManagement.beats.disenrolledBeatsNotificationTitle',
defaultMessage: '{beatsLength} beats disenrolled',
},
{
beatsLength: beats.length,
}
);
}
this.setState({
@ -241,18 +279,59 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
assignments: BeatsTagAssignment[],
tag: string
) => {
const actionName = action === 'remove' ? 'Removed' : 'Added';
const preposition = action === 'remove' ? 'from' : 'to';
const beatMessage =
assignments.length && assignments.length === 1
? `beat "${this.getNameForBeatId(assignments[0].beatId)}"`
: `${assignments.length} beats`;
const { intl } = this.props;
const notificationMessage =
action === 'remove'
? intl.formatMessage(
{
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({
notifications: this.state.notifications.concat({
color: 'success',
id: `tag-${moment.now()}`,
text: <p>{`${actionName} tag "${tag}" ${preposition} ${beatMessage}.`}</p>,
title: `Tag ${actionName}`,
text: <p>{notificationMessage}</p>,
title: notificationTitle,
}),
});
};
@ -311,3 +390,8 @@ export class BeatsPage extends React.PureComponent<BeatsPageProps, BeatsPageStat
// reduce to result
.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 { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import 'brace/mode/yaml';
import 'brace/theme/github';
@ -18,6 +19,7 @@ import { FrontendLibs } from '../../lib/lib';
interface TagPageProps extends URLStateProps<AppURLState> {
libs: FrontendLibs;
match: any;
intl: InjectedIntl;
}
interface TagPageState {
@ -25,7 +27,7 @@ interface TagPageState {
tag: BeatTag;
}
export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPageState> {
class CreateTagFragment extends React.PureComponent<TagPageProps, TagPageState> {
private mode: 'edit' | 'create' = 'create';
constructor(props: TagPageProps) {
super(props);
@ -79,7 +81,10 @@ export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPage
}
onClick={this.saveTag}
>
Save & Continue
<FormattedMessage
id="xpack.beatsManagement.createTag.saveAndContinueButtonLabel"
defaultMessage="Save & Continue"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
@ -97,9 +102,15 @@ export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPage
};
private saveTag = async () => {
const { intl } = this.props;
const newTag = await this.props.libs.tags.upsertTag(this.state.tag as BeatTag);
if (!newTag) {
return alert('error saving tag');
return alert(
intl.formatMessage({
id: 'xpack.beatsManagement.createTag.errorSavingTagTitle',
defaultMessage: 'error saving tag',
})
);
}
this.props.setUrlState({
createdTag: newTag.id,
@ -107,4 +118,6 @@ export class CreateTagFragment extends React.PureComponent<TagPageProps, TagPage
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,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { capitalize } from 'lodash';
import React from 'react';
import { RouteComponentProps } from 'react-router';
@ -26,6 +27,7 @@ import { FrontendLibs } from '../../lib/lib';
interface BeatsProps extends URLStateProps<AppURLState>, RouteComponentProps<any> {
match: any;
libs: FrontendLibs;
intl: InjectedIntl;
}
export class EnrollBeat extends React.Component<BeatsProps, any> {
private pinging = false;
@ -83,7 +85,7 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
if (this.props.urlState.enrollmentToken && !this.state.enrolledBeat) {
this.waitForToken(this.props.urlState.enrollmentToken);
}
const { goTo } = this.props;
const { goTo, intl } = this.props;
const actions = [];
@ -91,18 +93,27 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
case '/overview/initial/beats':
actions.push({
goTo: '/overview/initial/tag',
name: 'Continue',
name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.continueButtonLabel',
defaultMessage: 'Continue',
}),
});
break;
case '/overview/beats/enroll':
actions.push({
goTo: '/overview/beats/enroll',
name: 'Enroll another Beat',
name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.enrollAnotherBeatButtonLabel',
defaultMessage: 'Enroll another Beat',
}),
newToken: true,
});
actions.push({
goTo: '/overview/beats',
name: 'Done',
name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.doneButtonLabel',
defaultMessage: 'Done',
}),
clearToken: true,
});
break;
@ -117,15 +128,26 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>Beat type:</h3>
<h3>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.beatTypeTitle"
defaultMessage="Beat type:"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSelect
value={this.state.beatType}
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 })}
fullWidth={true}
@ -140,7 +162,12 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>Platform:</h3>
<h3>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.platformTitle"
defaultMessage="Platform:"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
@ -176,8 +203,13 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>
On the host where your {capitalize(this.state.beatType)} is installed,
run:
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.yourBeatTypeHostTitle"
defaultMessage="On the host where your {beatType} is installed, run:"
values={{
beatType: capitalize(this.state.beatType),
}}
/>
</h3>
</EuiTitle>
</EuiFlexItem>
@ -187,7 +219,14 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
className="euiFieldText euiFieldText--fullWidth"
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}
{this.props.libs.framework.baseURLPath
@ -203,7 +242,15 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<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>
</EuiFlexItem>
</EuiFlexGroup>
@ -218,7 +265,10 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
)}
{this.state.enrolledBeat && (
<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 />
@ -227,17 +277,32 @@ export class EnrollBeat extends React.Component<BeatsProps, any> {
columns={[
{
field: 'type',
name: 'Beat Type',
name: (
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.beatTypeColumnName"
defaultMessage="Beat Type"
/>
),
sortable: false,
},
{
field: 'version',
name: 'Version',
name: (
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.versionColumnName"
defaultMessage="Version"
/>
),
sortable: false,
},
{
field: 'host_name',
name: 'Hostname',
name: (
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.hostnameColumnName"
defaultMessage="Hostname"
/>
),
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.
*/
import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { BeatTag, CMBeat } from '../../../common/domain_types';
@ -14,6 +15,7 @@ import { FrontendLibs } from '../../lib/lib';
interface PageProps extends URLStateProps<AppURLState>, RouteComponentProps<any> {
loadBeats: any;
libs: FrontendLibs;
intl: InjectedIntl;
}
export class FinishWalkthrough extends React.Component<PageProps, any> {
constructor(props: PageProps) {
@ -47,10 +49,22 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
<EuiPageContent>
<EuiEmptyPrompt
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={
<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>
}
actions={
@ -61,7 +75,10 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
goTo('/overview/beats');
}}
>
Done
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.firstBeatEnrollingDoneButtonLabel"
defaultMessage="Done"
/>
</EuiButton>
}
/>
@ -75,16 +92,32 @@ export class FinishWalkthrough extends React.Component<PageProps, any> {
beats.map(({ id }) => ({ beatId: id, tag: tag.id }));
private assignTagToBeat = async () => {
const { intl } = this.props;
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) {
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);
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]);
@ -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,
} from '@elastic/eui';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { CMPopulatedBeat } from '../../../common/domain_types';
@ -32,6 +33,7 @@ import { TagsPage } from './tags';
interface MainPagesProps extends URLStateProps<AppURLState> {
libs: FrontendLibs;
location: any;
intl: InjectedIntl;
}
interface MainPagesState {
@ -68,6 +70,7 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
}
public render() {
const { intl } = this.props;
if (
this.state.loadedBeatsAtLeastOnce &&
this.state.unfilteredBeats.length === 0 &&
@ -78,7 +81,12 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
const tabs = [
{
id: '/overview/beats',
name: 'Enrolled Beats',
name: (
<FormattedMessage
id="xpack.beatsManagement.beats.enrolledBeatsTabTitle"
defaultMessage="Enrolled Beats"
/>
),
disabled: false,
},
// {
@ -88,7 +96,12 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
// },
{
id: '/overview/tags',
name: 'Configuration tags',
name: (
<FormattedMessage
id="xpack.beatsManagement.beats.configurationTagsTabTitle"
defaultMessage="Configuration tags"
/>
),
disabled: false,
},
];
@ -96,19 +109,28 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
const walkthroughSteps = [
{
id: '/overview/initial/beats',
name: 'Enroll Beat',
name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.enrollBeatStepLabel',
defaultMessage: 'Enroll Beat',
}),
disabled: false,
page: EnrollBeatPage,
},
{
id: '/overview/initial/tag',
name: 'Create tag',
name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.createTagStepLabel',
defaultMessage: 'Create tag',
}),
disabled: false,
page: CreateTagPageFragment,
},
{
id: '/overview/initial/finish',
name: 'finish',
name: intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.finishStepLabel',
defaultMessage: 'Finish',
}),
disabled: false,
page: FinishWalkthroughPage,
},
@ -117,16 +139,27 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
if (this.props.location.pathname === '/overview/initial/help') {
return (
<NoDataLayout
title="Beats central management (Beta)"
title={intl.formatMessage({
id: 'xpack.beatsManagement.enrollBeat.beatsCentralManagementTitle',
defaultMessage: 'Beats central management (Beta)',
})}
actionSection={
<ConnectedLink path="/overview/initial/beats">
<EuiButton color="primary" fill>
Enroll Beat
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.enrollBeatButtonLabel"
defaultMessage="Enroll Beat"
/>
</EuiButton>
</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>
);
}
@ -134,7 +167,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
if (this.props.location.pathname.includes('/overview/initial')) {
return (
<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}
goTo={this.props.goTo}
activePath={this.props.location.pathname}
@ -171,7 +207,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
return (
<PrimaryLayout
title="Beats"
title={intl.formatMessage({
id: 'xpack.beatsManagement.beatsRouteTitle',
defaultMessage: 'Beats',
})}
actionSection={
<Switch>
<Route
@ -192,7 +231,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
<EuiTabs>{renderedTabs}</EuiTabs>
<RouteWithBreadcrumb
title="Beats List"
title={intl.formatMessage({
id: 'xpack.beatsManagement.beatsListRouteTitle',
defaultMessage: 'Beats List',
})}
path="/overview/beats/:action?/:enrollmentToken?"
render={(props: any) => (
<BeatsPage
@ -205,7 +247,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
)}
/>
<RouteWithBreadcrumb
title="Activity Overview"
title={intl.formatMessage({
id: 'xpack.beatsManagement.activityOverviewRouteTitle',
defaultMessage: 'Activity Overview',
})}
path="/overview/activity"
exact={true}
render={(props: any) => (
@ -213,7 +258,10 @@ class MainPagesComponent extends React.PureComponent<MainPagesProps, MainPagesSt
)}
/>
<RouteWithBreadcrumb
title="Tags List"
title={intl.formatMessage({
id: 'xpack.beatsManagement.tagsListRouteTitle',
defaultMessage: 'Tags List',
})}
path="/overview/tags"
exact={true}
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 { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { BeatTag } from '../../../common/domain_types';
import { AppURLState } from '../../app';
@ -16,6 +17,7 @@ import { FrontendLibs } from '../../lib/lib';
interface TagsPageProps extends URLStateProps<AppURLState> {
libs: FrontendLibs;
intl: InjectedIntl;
}
interface TagsPageState {
@ -23,7 +25,7 @@ interface TagsPageState {
tableRef: any;
}
export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState> {
class TagsPageUi extends React.PureComponent<TagsPageProps, TagsPageState> {
public static ActionArea = ({ goTo }: TagsPageProps) => (
<EuiButton
size="s"
@ -32,7 +34,10 @@ export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState>
goTo('/tag/create');
}}
>
Add Tag
<FormattedMessage
id="xpack.beatsManagement.tags.addTagButtonLabel"
defaultMessage="Add Tag"
/>
</EuiButton>
);
@ -78,13 +83,18 @@ export class TagsPage extends React.PureComponent<TagsPageProps, TagsPageState>
}
private handleTagsAction = async (action: AssignmentActionType, payload: any) => {
const { intl } = this.props;
switch (action) {
case AssignmentActionType.Delete:
const tags = this.getSelectedTags().map((tag: BeatTag) => tag.id);
const success = await this.props.libs.tags.delete(tags);
if (!success) {
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 {
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;
* 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 { NoDataLayout } from '../components/layouts/no_data';
export const NoAccessPage: React.SFC<any> = () => (
<NoDataLayout title="Access denied" actionSection={[]}>
export const NoAccessPage = injectI18n(({ intl }) => (
<NoDataLayout
title={intl.formatMessage({
id: 'xpack.beatsManagement.noAccess.accessDeniedTitle',
defaultMessage: 'Access denied',
})}
actionSection={[]}
>
<p>
You are not authorized to access Beats central management. To use Beats central management,
you need the privileges granted by the `beats_admin` role.
<FormattedMessage
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>
</NoDataLayout>
);
));

View file

@ -9,6 +9,7 @@ import 'brace/theme/github';
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
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 React from 'react';
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> {
libs: FrontendLibs;
match: any;
intl: InjectedIntl;
}
interface TagPageState {
@ -56,9 +58,26 @@ export class TagPageComponent extends React.PureComponent<TagPageProps, TagPageS
}
}
public render() {
const { intl } = this.props;
return (
<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>
<TagEdit
@ -91,12 +110,18 @@ export class TagPageComponent extends React.PureComponent<TagPageProps, TagPageS
}
onClick={this.saveTag}
>
Save
<FormattedMessage
id="xpack.beatsManagement.tag.saveButtonLabel"
defaultMessage="Save"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={() => this.props.goTo('/overview/tags')}>
Cancel
<FormattedMessage
id="xpack.beatsManagement.tag.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
@ -141,4 +166,6 @@ export class TagPageComponent extends React.PureComponent<TagPageProps, TagPageS
.map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some(uniqueType => uniqueType === type))
.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.
*/
import { i18n } from '@kbn/i18n';
import React from 'react';
import { HashRouter, Redirect, Route, Switch } from 'react-router-dom';
import { Header } from './components/layouts/header';
@ -26,11 +27,15 @@ export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => {
breadcrumbs={[
{
href: '#/management',
text: 'Management',
text: i18n.translate('xpack.beatsManagement.router.managementTitle', {
defaultMessage: 'Management',
}),
},
{
href: '#/management/beats_management',
text: 'Beats',
text: i18n.translate('xpack.beatsManagement.router.beatsTitle', {
defaultMessage: 'Beats',
}),
},
...breadcrumbs,
]}
@ -55,11 +60,16 @@ export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => {
<Route path="/overview" render={(props: any) => <MainPages {...props} libs={libs} />} />
<RouteWithBreadcrumb
title={params => {
return `Beats: ${params.beatId}`;
return i18n.translate('xpack.beatsManagement.router.beatTitle', {
defaultMessage: 'Beats: {beatId}',
values: { beatId: params.beatId },
});
}}
parentBreadcrumbs={[
{
text: 'Beats List',
text: i18n.translate('xpack.beatsManagement.router.beatsListTitle', {
defaultMessage: 'Beats List',
}),
href: '#/management/beats_management/overview/beats',
},
]}
@ -69,13 +79,20 @@ export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => {
<RouteWithBreadcrumb
title={params => {
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={[
{
text: 'Tags List',
text: i18n.translate('xpack.beatsManagement.router.tagsListTitle', {
defaultMessage: 'Tags List',
}),
href: '#/management/beats_management/overview/tags',
},
]}