mirror of https://github.com/linkerd/linkerd2.git
Add more translations to dashboard and introduce i18n test wrapper (#5082)
Add more translations to the dashboard; introduce i18n test wrapper
This commit is contained in:
parent
15bd95ee1d
commit
79893c9ad6
|
@ -3,17 +3,18 @@ import { friendlyTitle, isResource, singularResource } from './util/Utils.js';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactRouterPropTypes from 'react-router-prop-types';
|
import ReactRouterPropTypes from 'react-router-prop-types';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import _chunk from 'lodash/chunk';
|
import _chunk from 'lodash/chunk';
|
||||||
import _takeWhile from 'lodash/takeWhile';
|
import _takeWhile from 'lodash/takeWhile';
|
||||||
import { withContext } from './util/AppContext.jsx';
|
import { withContext } from './util/AppContext.jsx';
|
||||||
|
|
||||||
const routeToCrumbTitle = {
|
const routeToCrumbTitle = {
|
||||||
controlplane: 'Control Plane',
|
controlplane: <Trans>menuItemControlPlane</Trans>,
|
||||||
overview: 'Overview',
|
tap: <Trans>menuItemTap</Trans>,
|
||||||
tap: 'Tap',
|
top: <Trans>menuItemTop</Trans>,
|
||||||
top: 'Top',
|
routes: <Trans>menuItemRoutes</Trans>,
|
||||||
routes: 'Top Routes',
|
community: <Trans>menuItemCommunity</Trans>,
|
||||||
community: 'Community',
|
gateways: <Trans>menuItemGateway</Trans>,
|
||||||
};
|
};
|
||||||
|
|
||||||
class BreadcrumbHeader extends React.Component {
|
class BreadcrumbHeader extends React.Component {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { BrowserRouter } from 'react-router-dom';
|
||||||
import BreadcrumbHeader from './BreadcrumbHeader.jsx';
|
import BreadcrumbHeader from './BreadcrumbHeader.jsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||||
|
|
||||||
const loc = {
|
const loc = {
|
||||||
pathname: '',
|
pathname: '',
|
||||||
|
@ -48,13 +49,13 @@ describe('Tests for <BreadcrumbHeader>', () => {
|
||||||
it("renders correct breadcrumb text for top-level pages [Control Plane]",
|
it("renders correct breadcrumb text for top-level pages [Control Plane]",
|
||||||
() => {
|
() => {
|
||||||
loc.pathname = "/controlplane";
|
loc.pathname = "/controlplane";
|
||||||
const component = mount(
|
const component = mount(i18nWrap(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<BreadcrumbHeader
|
<BreadcrumbHeader
|
||||||
location={loc}
|
location={loc}
|
||||||
pathPrefix="" />
|
pathPrefix="" />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
));
|
||||||
const crumbs = component.find("span");
|
const crumbs = component.find("span");
|
||||||
expect(crumbs).toHaveLength(1);
|
expect(crumbs).toHaveLength(1);
|
||||||
const crumbText = crumbs.reduce((acc, crumb) => {
|
const crumbText = crumbs.reduce((acc, crumb) => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Plural, Trans } from '@lingui/macro';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Step from '@material-ui/core/Step';
|
import Step from '@material-ui/core/Step';
|
||||||
|
@ -17,9 +18,9 @@ const styles = theme => ({
|
||||||
|
|
||||||
function getSteps(numResources, resource) {
|
function getSteps(numResources, resource) {
|
||||||
return [
|
return [
|
||||||
{ label: 'Controller successfully installed' },
|
{ label: <Trans>controllerInstalledMsg</Trans>, key: 'installedMsg' },
|
||||||
{ label: `${numResources || 'No'} ${resource}s detected` },
|
{ label: <Plural value={numResources} zero={resource} one={resource} other={resource} />, key: 'resourceMsg' },
|
||||||
{ label: `Connect your first ${resource}`, content: incompleteMeshMessage() },
|
{ label: <Trans>connectResourceMsg {resource}</Trans>, content: incompleteMeshMessage(), key: 'connectMsg' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ const CallToAction = ({ resource, numResources, classes }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Typography>The service mesh was successfully installed!</Typography>
|
<Typography><Trans>serviceMeshInstalledMsg</Trans></Typography>
|
||||||
<Stepper
|
<Stepper
|
||||||
activeStep={lastStep}
|
activeStep={lastStep}
|
||||||
className={classes.instructions}
|
className={classes.instructions}
|
||||||
|
@ -40,7 +41,7 @@ const CallToAction = ({ resource, numResources, classes }) => {
|
||||||
props.completed = i < lastStep; // select the last step as the currently active one
|
props.completed = i < lastStep; // select the last step as the currently active one
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Step key={step.label} {...props}>
|
<Step key={step.key} {...props}>
|
||||||
<StepLabel>{step.label}</StepLabel>
|
<StepLabel>{step.label}</StepLabel>
|
||||||
<StepContent>{step.content}</StepContent>
|
<StepContent>{step.content}</StepContent>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
|
@ -285,7 +285,7 @@ class CheckModal extends React.Component {
|
||||||
{success !== undefined &&
|
{success !== undefined &&
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<SimpleChip
|
<SimpleChip
|
||||||
label={success ? 'Success' : 'Error'}
|
label={success ? <Trans>labelSuccess</Trans> : <Trans>labelError</Trans>}
|
||||||
type={success ? 'good' : 'bad'} />
|
type={success ? 'good' : 'bad'} />
|
||||||
</Grid>
|
</Grid>
|
||||||
}
|
}
|
||||||
|
@ -316,11 +316,11 @@ class CheckModal extends React.Component {
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={this.runCheck} color="primary">
|
<Button onClick={this.runCheck} color="primary">
|
||||||
Re-Run Check
|
<Trans>buttonReRunCheck</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={this.handleOpenChange} color="primary">
|
<Button onClick={this.handleOpenChange} color="primary">
|
||||||
Close
|
<Trans>buttonClose</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import InputLabel from '@material-ui/core/InputLabel';
|
||||||
import NoteAddIcon from '@material-ui/icons/NoteAdd';
|
import NoteAddIcon from '@material-ui/icons/NoteAdd';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import _isEmpty from 'lodash/isEmpty';
|
import _isEmpty from 'lodash/isEmpty';
|
||||||
import { withContext } from './util/AppContext.jsx';
|
import { withContext } from './util/AppContext.jsx';
|
||||||
|
@ -131,7 +132,7 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={this.handleClickOpen}>
|
onClick={this.handleClickOpen}>
|
||||||
Create Service Profile
|
<Trans>buttonCreateServiceProfile</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -144,7 +145,7 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
disabled={disableDownloadButton}
|
disabled={disableDownloadButton}
|
||||||
onClick={() => this.handleClose(downloadUrl)}
|
onClick={() => this.handleClose(downloadUrl)}
|
||||||
color="primary">
|
color="primary">
|
||||||
Download
|
<Trans>buttonDownload</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -155,11 +156,12 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
open={open}
|
open={open}
|
||||||
onClose={this.handleClose}
|
onClose={this.handleClose}
|
||||||
aria-labelledby="form-dialog-title">
|
aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">New service profile</DialogTitle>
|
<DialogTitle id="form-dialog-title">
|
||||||
|
<Trans>New service profile</Trans>
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
To create a service profile, download a profile and then apply it
|
<Trans>formCreateServiceProfileHelpText</Trans>
|
||||||
with `kubectl apply`.
|
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<FormControl
|
<FormControl
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
|
@ -173,8 +175,7 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
aria-describedby="component-error-text" />
|
aria-describedby="component-error-text" />
|
||||||
{error.service && (
|
{error.service && (
|
||||||
<FormHelperText id="component-error-text">
|
<FormHelperText id="component-error-text">
|
||||||
Service name must consist of lower case alphanumeric characters or -
|
<Trans>formServiceNameErrorText</Trans>
|
||||||
start with an alphabetic character, and end with an alphanumeric character
|
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -190,15 +191,14 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
aria-describedby="component-error-text" />
|
aria-describedby="component-error-text" />
|
||||||
{error.namespace && (
|
{error.namespace && (
|
||||||
<FormHelperText id="component-error-text">
|
<FormHelperText id="component-error-text">
|
||||||
Namespace must consist of lower case alphanumeric characters or -
|
<Trans>formNamespaceErrorText</Trans>
|
||||||
and must start and end with an alphanumeric character
|
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={this.handleClose} color="primary">
|
<Button onClick={this.handleClose} color="primary">
|
||||||
Cancel
|
<Trans>buttonCancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
{disableDownloadButton ?
|
{disableDownloadButton ?
|
||||||
downloadButton :
|
downloadButton :
|
||||||
|
@ -221,9 +221,7 @@ class ConfigureProfilesMsg extends React.Component {
|
||||||
return (
|
return (
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography component="div">
|
<Typography component="div">
|
||||||
No named route traffic found. This could be because the service is
|
<Trans>formNoNamedRouteTrafficFound</Trans>
|
||||||
not receiving any traffic, or because there is no service profile
|
|
||||||
configured. Does the service have a service profile?
|
|
||||||
{this.renderDownloadProfileForm()}
|
{this.renderDownloadProfileForm()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import TableBody from '@material-ui/core/TableBody';
|
||||||
import TableCell from '@material-ui/core/TableCell';
|
import TableCell from '@material-ui/core/TableCell';
|
||||||
import TableHead from '@material-ui/core/TableHead';
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
import TableRow from '@material-ui/core/TableRow';
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
|
@ -131,7 +132,7 @@ class ExpandableTable extends React.Component {
|
||||||
open={open}
|
open={open}
|
||||||
onClose={this.handleDialogClose}
|
onClose={this.handleDialogClose}
|
||||||
aria-labelledby="form-dialog-title">
|
aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle id="form-dialog-title">Request Details</DialogTitle>
|
<DialogTitle id="form-dialog-title"><Trans>tableTitleRequestDetails</Trans></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{expandedRowRender(datum, classes.expandedWrap)}
|
{expandedRowRender(datum, classes.expandedWrap)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
@ -99,7 +99,7 @@ class Gateways extends React.Component {
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
{noMetrics ? <div>No resources detected.</div> : null}
|
{noMetrics ? <div><Trans>noResourcesDetectedMsg</Trans></div> : null}
|
||||||
{noMetrics ? null : (
|
{noMetrics ? null : (
|
||||||
<div className="page-section">
|
<div className="page-section">
|
||||||
<MetricsTable
|
<MetricsTable
|
||||||
|
|
|
@ -356,7 +356,7 @@ class Octopus extends React.Component {
|
||||||
|
|
||||||
Octopus.propTypes = {
|
Octopus.propTypes = {
|
||||||
api: PropTypes.shape({
|
api: PropTypes.shape({
|
||||||
ResourceLink: PropTypes.element,
|
ResourceLink: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
|
||||||
}),
|
}),
|
||||||
neighbors: PropTypes.shape({}),
|
neighbors: PropTypes.shape({}),
|
||||||
resource: PropTypes.shape({}),
|
resource: PropTypes.shape({}),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import CardContent from '@material-ui/core/CardContent';
|
import CardContent from '@material-ui/core/CardContent';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import _isEmpty from 'lodash/isEmpty';
|
import _isEmpty from 'lodash/isEmpty';
|
||||||
import _startCase from 'lodash/startCase';
|
import _startCase from 'lodash/startCase';
|
||||||
|
@ -32,11 +33,13 @@ class QueryToCliCmd extends React.Component {
|
||||||
render = () => {
|
render = () => {
|
||||||
const { cmdName, query, resource, controllerNamespace } = this.props;
|
const { cmdName, query, resource, controllerNamespace } = this.props;
|
||||||
|
|
||||||
|
const cmdNameDisplay = _startCase(cmdName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
_isEmpty(resource) ? null :
|
_isEmpty(resource) ? null :
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="caption" gutterBottom>
|
<Typography variant="caption" gutterBottom>
|
||||||
Current {_startCase(cmdName)} query
|
<Trans>Current {cmdNameDisplay} query</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import QueryToCliCmd from './QueryToCliCmd.jsx';
|
import QueryToCliCmd from './QueryToCliCmd.jsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||||
|
|
||||||
describe('QueryToCliCmd', () => {
|
describe('QueryToCliCmd', () => {
|
||||||
it('renders a query as a linkerd CLI command', () => {
|
it('renders a query as a linkerd CLI command', () => {
|
||||||
|
@ -10,12 +11,12 @@ describe('QueryToCliCmd', () => {
|
||||||
"scheme": ""
|
"scheme": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = mount(
|
let component = mount(i18nWrap(
|
||||||
<QueryToCliCmd
|
<QueryToCliCmd
|
||||||
cmdName="routes"
|
cmdName="routes"
|
||||||
query={query}
|
query={query}
|
||||||
resource={query.resource}
|
resource={query.resource}
|
||||||
controllerNamespace="linkerd" />
|
controllerNamespace="linkerd" />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Current Routes query");
|
expect(component).toIncludeText("Current Routes query");
|
||||||
|
@ -28,12 +29,12 @@ describe('QueryToCliCmd', () => {
|
||||||
"namespace": "linkerd"
|
"namespace": "linkerd"
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = mount(
|
let component = mount(i18nWrap(
|
||||||
<QueryToCliCmd
|
<QueryToCliCmd
|
||||||
cmdName="routes"
|
cmdName="routes"
|
||||||
query={query}
|
query={query}
|
||||||
resource={query.resource}
|
resource={query.resource}
|
||||||
controllerNamespace="my-linkerd-ns" />
|
controllerNamespace="my-linkerd-ns" />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Current Routes query");
|
expect(component).toIncludeText("Current Routes query");
|
||||||
|
@ -49,12 +50,12 @@ describe('QueryToCliCmd', () => {
|
||||||
"authority": "foo.bar:8080"
|
"authority": "foo.bar:8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = mount(
|
let component = mount(i18nWrap(
|
||||||
<QueryToCliCmd
|
<QueryToCliCmd
|
||||||
cmdName="tap"
|
cmdName="tap"
|
||||||
query={query}
|
query={query}
|
||||||
resource={query.resource}
|
resource={query.resource}
|
||||||
controllerNamespace="linkerd" />
|
controllerNamespace="linkerd" />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Current Tap query");
|
expect(component).toIncludeText("Current Tap query");
|
||||||
|
@ -71,12 +72,12 @@ describe('QueryToCliCmd', () => {
|
||||||
"authority": "foo.bar:8080"
|
"authority": "foo.bar:8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = mount(
|
let component = mount(i18nWrap(
|
||||||
<QueryToCliCmd
|
<QueryToCliCmd
|
||||||
cmdName="tap"
|
cmdName="tap"
|
||||||
query={query}
|
query={query}
|
||||||
resource={query.resource}
|
resource={query.resource}
|
||||||
controllerNamespace="linkerd" />
|
controllerNamespace="linkerd" />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Current Tap query");
|
expect(component).toIncludeText("Current Tap query");
|
||||||
|
@ -89,12 +90,12 @@ describe('QueryToCliCmd', () => {
|
||||||
"namespace": "linkerd"
|
"namespace": "linkerd"
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = mount(
|
let component = mount(i18nWrap(
|
||||||
<QueryToCliCmd
|
<QueryToCliCmd
|
||||||
cmdName="top"
|
cmdName="top"
|
||||||
query={query}
|
query={query}
|
||||||
resource={query.resource}
|
resource={query.resource}
|
||||||
controllerNamespace="linkerd" />
|
controllerNamespace="linkerd" />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Current Top query");
|
expect(component).toIncludeText("Current Top query");
|
||||||
|
@ -109,12 +110,12 @@ describe('QueryToCliCmd', () => {
|
||||||
"theLimitDoesNotExist": 999
|
"theLimitDoesNotExist": 999
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = mount(
|
let component = mount(i18nWrap(
|
||||||
<QueryToCliCmd
|
<QueryToCliCmd
|
||||||
cmdName="tap"
|
cmdName="tap"
|
||||||
query={query}
|
query={query}
|
||||||
resource={query.resource}
|
resource={query.resource}
|
||||||
controllerNamespace="linkerd" />
|
controllerNamespace="linkerd" />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Current Tap query");
|
expect(component).toIncludeText("Current Tap query");
|
||||||
|
|
|
@ -367,11 +367,11 @@ export class ResourceDetailBase extends React.Component {
|
||||||
<Grid item><Typography variant="h5">{resourceType}/{resourceName}</Typography></Grid>
|
<Grid item><Typography variant="h5">{resourceType}/{resourceName}</Typography></Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
{showNoTrafficMsg ? <Grid item><SimpleChip label="no traffic" type="warning" /></Grid> : null}
|
{showNoTrafficMsg ? <Grid item><SimpleChip label={<Trans>columnTitleNoTraffic</Trans>} type="warning" /></Grid> : null}
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{resourceIsMeshed ?
|
{resourceIsMeshed ?
|
||||||
<SimpleChip label="meshed" type="good" /> :
|
<SimpleChip label={<Trans>columnTitleMeshed</Trans>} type="good" /> :
|
||||||
<SimpleChip label="unmeshed" type="bad" />
|
<SimpleChip label={<Trans>columnTitleUnmeshed</Trans>} type="bad" />
|
||||||
}
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Plural, Trans } from '@lingui/macro';
|
||||||
import { formatDistanceToNow, subSeconds } from 'date-fns';
|
import { formatDistanceToNow, subSeconds } from 'date-fns';
|
||||||
import { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';
|
import { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';
|
||||||
import BaseTable from './BaseTable.jsx';
|
import BaseTable from './BaseTable.jsx';
|
||||||
|
@ -13,7 +14,6 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Spinner from './util/Spinner.jsx';
|
import Spinner from './util/Spinner.jsx';
|
||||||
import StatusTable from './StatusTable.jsx';
|
import StatusTable from './StatusTable.jsx';
|
||||||
import { Trans } from '@lingui/macro';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import _compact from 'lodash/compact';
|
import _compact from 'lodash/compact';
|
||||||
import _countBy from 'lodash/countBy';
|
import _countBy from 'lodash/countBy';
|
||||||
|
@ -107,7 +107,7 @@ class ServiceMesh extends React.Component {
|
||||||
const { productName, releaseVersion, controllerNamespace } = this.props;
|
const { productName, releaseVersion, controllerNamespace } = this.props;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ key: 1, name: `${productName} version`, value: releaseVersion },
|
{ key: 1, name: <Trans>{productName} version</Trans>, value: releaseVersion },
|
||||||
{ key: 2, name: <Trans>{productName} namespace</Trans>, value: controllerNamespace },
|
{ key: 2, name: <Trans>{productName} namespace</Trans>, value: controllerNamespace },
|
||||||
{ key: 3, name: <Trans>Control plane components</Trans>, value: components.length },
|
{ key: 3, name: <Trans>Control plane components</Trans>, value: components.length },
|
||||||
{ key: 4, name: <Trans>Data plane proxies</Trans>, value: this.proxyCount() },
|
{ key: 4, name: <Trans>Data plane proxies</Trans>, value: this.proxyCount() },
|
||||||
|
@ -223,7 +223,7 @@ class ServiceMesh extends React.Component {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Grid container justify="space-between">
|
<Grid container justify="space-between">
|
||||||
<Grid item xs={3}>
|
<Grid item xs={3}>
|
||||||
<Typography variant="h6">Control plane</Typography>
|
<Typography variant="h6"><Trans>Control plane</Trans></Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={3}>
|
<Grid item xs={3}>
|
||||||
<Typography align="right"><Trans>componentsMsg</Trans></Typography>
|
<Typography align="right"><Trans>componentsMsg</Trans></Typography>
|
||||||
|
@ -243,7 +243,7 @@ class ServiceMesh extends React.Component {
|
||||||
renderServiceMeshDetails() {
|
renderServiceMeshDetails() {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Typography variant="h6">Service mesh details</Typography>
|
<Typography variant="h6"><Trans>Service mesh details</Trans></Typography>
|
||||||
|
|
||||||
<BaseTable
|
<BaseTable
|
||||||
tableClassName="metric-table"
|
tableClassName="metric-table"
|
||||||
|
@ -263,14 +263,18 @@ class ServiceMesh extends React.Component {
|
||||||
let numUnadded = 0;
|
let numUnadded = 0;
|
||||||
|
|
||||||
if (_isEmpty(nsStatuses)) {
|
if (_isEmpty(nsStatuses)) {
|
||||||
message = 'No resources detected.';
|
message = <Trans>noNamespacesDetectedMsg</Trans>;
|
||||||
} else {
|
} else {
|
||||||
const meshedCount = _countBy(nsStatuses, pod => {
|
const meshedCount = _countBy(nsStatuses, pod => {
|
||||||
return pod.meshedPercent.get() > 0;
|
return pod.meshedPercent.get() > 0;
|
||||||
});
|
});
|
||||||
numUnadded = meshedCount.false || 0;
|
numUnadded = meshedCount.false || 0;
|
||||||
message = numUnadded === 0 ? `All namespaces have a ${productName} install.` :
|
message = numUnadded === 0 ? <Trans>All namespaces have a {productName} install.</Trans> : (
|
||||||
`${numUnadded} ${numUnadded === 1 ? 'namespace has' : 'namespaces have'} no meshed resources.`;
|
<Plural
|
||||||
|
value={numUnadded}
|
||||||
|
one="# namespace has no meshed resources"
|
||||||
|
other="# namespaces have no meshed resources" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -8,6 +8,7 @@ import sinon from 'sinon';
|
||||||
import sinonStubPromise from 'sinon-stub-promise';
|
import sinonStubPromise from 'sinon-stub-promise';
|
||||||
import Spinner from './util/Spinner.jsx';
|
import Spinner from './util/Spinner.jsx';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||||
|
|
||||||
sinonStubPromise(sinon);
|
sinonStubPromise(sinon);
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve({ metrics: [] })
|
json: () => Promise.resolve({ metrics: [] })
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
component.update();
|
component.update();
|
||||||
|
@ -107,7 +108,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve({ metrics: [] })
|
json: () => Promise.resolve({ metrics: [] })
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
component.update();
|
component.update();
|
||||||
|
@ -122,7 +123,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve({ metrics: [] })
|
json: () => Promise.resolve({ metrics: [] })
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
component.update();
|
component.update();
|
||||||
|
@ -138,7 +139,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve({})
|
json: () => Promise.resolve({})
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
expect(component).toIncludeText("No namespaces detected");
|
expect(component).toIncludeText("No namespaces detected");
|
||||||
|
@ -163,7 +164,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve(nsAllResourcesAdded)
|
json: () => Promise.resolve(nsAllResourcesAdded)
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
expect(component).toIncludeText("4 namespaces have no meshed resources.");
|
expect(component).toIncludeText("4 namespaces have no meshed resources.");
|
||||||
|
@ -183,7 +184,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve(nsOneResourceNotAdded)
|
json: () => Promise.resolve(nsOneResourceNotAdded)
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
expect(component).toIncludeText("1 namespace has no meshed resources.");
|
expect(component).toIncludeText("1 namespace has no meshed resources.");
|
||||||
|
@ -200,7 +201,7 @@ describe('ServiceMesh', () => {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve(nsAllResourcesAdded)
|
json: () => Promise.resolve(nsAllResourcesAdded)
|
||||||
});
|
});
|
||||||
component = mount(routerWrap(ServiceMesh));
|
component = mount(i18nWrap(routerWrap(ServiceMesh)));
|
||||||
|
|
||||||
return withPromise(() => {
|
return withPromise(() => {
|
||||||
expect(component).toIncludeText("All namespaces have a ShinyProductName install.");
|
expect(component).toIncludeText("All namespaces have a ShinyProductName install.");
|
||||||
|
|
|
@ -25,18 +25,18 @@ const columnConfig = {
|
||||||
width: 200,
|
width: 200,
|
||||||
wrapDotsAt: 7, // dots take up more than one line in the table; space them out
|
wrapDotsAt: 7, // dots take up more than one line in the table; space them out
|
||||||
dotExplanation: status => {
|
dotExplanation: status => {
|
||||||
return status.value === 'good' ? 'is up and running' : 'has not been started';
|
return status.value === 'good' ? <Trans>statusExplanationGood</Trans> : <Trans>statusExplanationNotStarted</Trans>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Proxy Status': {
|
'Proxy Status': {
|
||||||
width: 250,
|
width: 250,
|
||||||
wrapDotsAt: 9,
|
wrapDotsAt: 9,
|
||||||
dotExplanation: pod => {
|
dotExplanation: pod => {
|
||||||
const addedStatus = !pod.added ? 'Not in mesh' : 'Added to mesh';
|
const addedStatus = !pod.added ? <Trans>statusExplanationNotInMesh</Trans> : <Trans>statusExplanationInMesh</Trans>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div>Pod status: {pod.status}</div>
|
<div><Trans>Pod status: {pod.status}</Trans></div>
|
||||||
<div>{addedStatus}</div>
|
<div>{addedStatus}</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -138,39 +138,39 @@ const itemDisplay = (title, value) => {
|
||||||
|
|
||||||
const requestInitSection = d => (
|
const requestInitSection = d => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Typography variant="subtitle2">Request Init</Typography>
|
<Typography variant="subtitle2"><Trans>tableTitleRequestInit</Trans></Typography>
|
||||||
<br />
|
<br />
|
||||||
<List dense>
|
<List dense>
|
||||||
{itemDisplay('Authority', _get(d, 'requestInit.http.requestInit.authority'))}
|
{itemDisplay(<Trans>formAuthority</Trans>, _get(d, 'requestInit.http.requestInit.authority'))}
|
||||||
{itemDisplay('Path', _get(d, 'requestInit.http.requestInit.path'))}
|
{itemDisplay(<Trans>formPath</Trans>, _get(d, 'requestInit.http.requestInit.path'))}
|
||||||
{itemDisplay('Scheme', _get(d, 'requestInit.http.requestInit.scheme.registered'))}
|
{itemDisplay(<Trans>formScheme</Trans>, _get(d, 'requestInit.http.requestInit.scheme.registered'))}
|
||||||
{itemDisplay('Method', _get(d, 'requestInit.http.requestInit.method.registered'))}
|
{itemDisplay(<Trans>formMethod</Trans>, _get(d, 'requestInit.http.requestInit.method.registered'))}
|
||||||
{headersDisplay('Headers', _get(d, 'requestInit.http.requestInit.headers'))}
|
{headersDisplay(<Trans>formHeaders</Trans>, _get(d, 'requestInit.http.requestInit.headers'))}
|
||||||
</List>
|
</List>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
const responseInitSection = d => _isEmpty(d.responseInit) ? null : (
|
const responseInitSection = d => _isEmpty(d.responseInit) ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Typography variant="subtitle2">Response Init</Typography>
|
<Typography variant="subtitle2"><Trans>tableTitleResponseInit</Trans></Typography>
|
||||||
<br />
|
<br />
|
||||||
<List dense>
|
<List dense>
|
||||||
{itemDisplay('HTTP Status', _get(d, 'responseInit.http.responseInit.httpStatus'))}
|
{itemDisplay(<Trans>formHTTPStatus</Trans>, _get(d, 'responseInit.http.responseInit.httpStatus'))}
|
||||||
{itemDisplay('Latency', formatTapLatency(_get(d, 'responseInit.http.responseInit.sinceRequestInit')))}
|
{itemDisplay(<Trans>formLatency</Trans>, formatTapLatency(_get(d, 'responseInit.http.responseInit.sinceRequestInit')))}
|
||||||
{headersDisplay('Headers', _get(d, 'responseInit.http.responseInit.headers'))}
|
{headersDisplay(<Trans>formHeaders</Trans>, _get(d, 'responseInit.http.responseInit.headers'))}
|
||||||
</List>
|
</List>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
const responseEndSection = d => _isEmpty(d.responseEnd) ? null : (
|
const responseEndSection = d => _isEmpty(d.responseEnd) ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Typography variant="subtitle2">Response End</Typography>
|
<Typography variant="subtitle2"><Trans>tableTitleResponseEnd</Trans></Typography>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<List dense>
|
<List dense>
|
||||||
{itemDisplay('GRPC Status', _isNull(_get(d, 'responseEnd.http.responseEnd.eos')) ? 'N/A' : grpcStatusCodes[_get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode')])}
|
{itemDisplay(<Trans>formGRPCStatus</Trans>, _isNull(_get(d, 'responseEnd.http.responseEnd.eos')) ? 'N/A' : grpcStatusCodes[_get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode')])}
|
||||||
{itemDisplay('Latency', formatTapLatency(_get(d, 'responseEnd.http.responseEnd.sinceResponseInit')))}
|
{itemDisplay(<Trans>formLatency</Trans>, formatTapLatency(_get(d, 'responseEnd.http.responseEnd.sinceResponseInit')))}
|
||||||
{itemDisplay('Response Length (B)', formatWithComma(_get(d, 'responseEnd.http.responseEnd.responseBytes')))}
|
{itemDisplay(<Trans>formResponseLengthB</Trans>, formatWithComma(_get(d, 'responseEnd.http.responseEnd.responseBytes')))}
|
||||||
</List>
|
</List>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,7 +36,6 @@ import _noop from 'lodash/noop';
|
||||||
import _omit from 'lodash/omit';
|
import _omit from 'lodash/omit';
|
||||||
import _pick from 'lodash/pick';
|
import _pick from 'lodash/pick';
|
||||||
import _some from 'lodash/some';
|
import _some from 'lodash/some';
|
||||||
import _startCase from 'lodash/startCase';
|
|
||||||
import _uniq from 'lodash/uniq';
|
import _uniq from 'lodash/uniq';
|
||||||
import _values from 'lodash/values';
|
import _values from 'lodash/values';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
@ -245,7 +244,7 @@ class TapQueryForm extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<InputLabel htmlFor={resourceKey}>{_startCase(resourceKey)}</InputLabel>
|
<InputLabel htmlFor={resourceKey}>{resourceKey === 'resource' ? <Trans>formResource</Trans> : <Trans>formToResource</Trans>}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={!nsEmpty && resourceOptions.includes(query[resourceKey]) ? query[resourceKey] : ''}
|
value={!nsEmpty && resourceOptions.includes(query[resourceKey]) ? query[resourceKey] : ''}
|
||||||
onChange={this.handleFormChange(resourceKey)}
|
onChange={this.handleFormChange(resourceKey)}
|
||||||
|
@ -364,20 +363,20 @@ class TapQueryForm extends React.Component {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
<FormHelperText>Display requests with this :authority</FormHelperText>
|
<FormHelperText><Trans>formAuthorityHelpText</Trans></FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||||
{ this.renderTextInput(<Trans>formPath</Trans>, 'path', 'Display requests with paths that start with this prefix') }
|
{ this.renderTextInput(<Trans>formPath</Trans>, 'path', <Trans>formPathHelpText</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||||
{ this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', 'Display requests with this scheme') }
|
{ this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', <Trans>formSchemeHelpText</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||||
{ this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', `Maximum requests per second to tap. Default ${defaultMaxRps}`) }
|
{ this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', <Trans>formMaxRPSHelpText {defaultMaxRps}</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
<Grid item xs={6} md={3} className={classes.formControlWrapper}>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl className={classes.formControl}>
|
||||||
|
@ -393,7 +392,7 @@ class TapQueryForm extends React.Component {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
<FormHelperText>Display requests with this HTTP method</FormHelperText>
|
<FormHelperText><Trans>formHTTPMethodHelpText</Trans></FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -203,11 +203,11 @@ class TopRoutes extends React.Component {
|
||||||
<Grid container direction="column" spacing={2}>
|
<Grid container direction="column" spacing={2}>
|
||||||
<Grid item container spacing={4} alignItems="center" justify="flex-start">
|
<Grid item container spacing={4} alignItems="center" justify="flex-start">
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{ this.renderNamespaceDropdown('Namespace', 'namespace', 'Namespace to query') }
|
{ this.renderNamespaceDropdown(<Trans>formNamespace</Trans>, 'namespace', <Trans>formNamespaceHelpText</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{ this.renderResourceDropdown('Resource', 'resource_name', 'resource_type', 'Resource to query') }
|
{ this.renderResourceDropdown(<Trans>formResource</Trans>, 'resource_name', 'resource_type', <Trans>formResourceHelpText</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
|
@ -216,7 +216,7 @@ class TopRoutes extends React.Component {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
disabled={requestInProgress || !query.namespace || !query.resource_type}
|
disabled={requestInProgress || !query.namespace || !query.resource_type}
|
||||||
onClick={this.handleBtnClick(true)}>
|
onClick={this.handleBtnClick(true)}>
|
||||||
Start
|
<Trans>buttonStart</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
@ -226,23 +226,23 @@ class TopRoutes extends React.Component {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
disabled={!requestInProgress}
|
disabled={!requestInProgress}
|
||||||
onClick={this.handleBtnClick(false)}>
|
onClick={this.handleBtnClick(false)}>
|
||||||
Stop
|
<Trans>buttonStop</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item container spacing={4} alignItems="center" justify="flex-start">
|
<Grid item container spacing={4} alignItems="center" justify="flex-start">
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{ this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', 'Namespace of target resource') }
|
{ this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', <Trans>formToNamespaceHelpText</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{ this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', 'Target resource') }
|
{ this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', <Trans>formToResourceHelpText</Trans>) }
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider light className={classes.root} />
|
<Divider light className={classes.root} />
|
||||||
<Typography variant="caption">You can also create a new profile <ConfigureProfilesMsg showAsIcon /></Typography>
|
<Typography variant="caption"><Trans>createNewProfileMsg</Trans> <ConfigureProfilesMsg showAsIcon /></Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Tab from '@material-ui/core/Tab';
|
||||||
import Tabs from '@material-ui/core/Tabs';
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
import TopModule from './TopModule.jsx';
|
import TopModule from './TopModule.jsx';
|
||||||
import TopRoutesModule from './TopRoutesModule.jsx';
|
import TopRoutesModule from './TopRoutesModule.jsx';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import _isEmpty from 'lodash/isEmpty';
|
import _isEmpty from 'lodash/isEmpty';
|
||||||
import _noop from 'lodash/noop';
|
import _noop from 'lodash/noop';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
@ -91,8 +92,8 @@ class TopRoutesTabs extends React.Component {
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
indicatorColor="primary"
|
indicatorColor="primary"
|
||||||
textColor="primary">
|
textColor="primary">
|
||||||
<Tab label="Live Calls" />
|
<Tab label={<Trans>tabLiveCalls</Trans>} />
|
||||||
<Tab label="Route Metrics" />
|
<Tab label={<Trans>tabRouteMetrics</Trans>} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
{value === 0 && this.renderTopComponent()}
|
{value === 0 && this.renderTopComponent()}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import { apiErrorPropType } from './util/ApiHelpers.jsx';
|
import { apiErrorPropType } from './util/ApiHelpers.jsx';
|
||||||
import { withContext } from './util/AppContext.jsx';
|
import { withContext } from './util/AppContext.jsx';
|
||||||
|
@ -39,25 +40,31 @@ class Version extends React.Component {
|
||||||
if (!latestVersion) {
|
if (!latestVersion) {
|
||||||
return (
|
return (
|
||||||
<Typography className={classes.versionMsg}>
|
<Typography className={classes.versionMsg}>
|
||||||
Version check failed{error ? `: ${error.statusText}` : ''}.
|
<Trans>Version check failed{error ? `: ${error.statusText}` : ''}.</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLatest) {
|
if (isLatest) {
|
||||||
return <Typography className={classes.versionMsg}>Linkerd is up to date.</Typography>;
|
return <Typography className={classes.versionMsg}><Trans>LinkerdIsUpToDateMsg</Trans></Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const versionText = this.numericVersion(latestVersion);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography className={classes.versionMsg}>A new version ({this.numericVersion(latestVersion)}) is available.</Typography>
|
<Typography className={classes.versionMsg}>
|
||||||
|
<Trans>
|
||||||
|
A new version ({versionText}) is available.
|
||||||
|
</Trans>
|
||||||
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
className={classes.updateBtn}
|
className={classes.updateBtn}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://versioncheck.linkerd.io/update">
|
href="https://versioncheck.linkerd.io/update">
|
||||||
Update Now
|
<Trans>Update Now</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -66,7 +73,7 @@ class Version extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const { classes, releaseVersion, productName } = this.props;
|
const { classes, releaseVersion, productName } = this.props;
|
||||||
const channel = this.versionChannel(releaseVersion);
|
const channel = this.versionChannel(releaseVersion);
|
||||||
let message = `Running ${productName || 'controller'}`;
|
let message = ` ${productName || 'controller'}`;
|
||||||
message += ` ${this.numericVersion(releaseVersion)}`;
|
message += ` ${this.numericVersion(releaseVersion)}`;
|
||||||
if (channel) {
|
if (channel) {
|
||||||
message += ` (${channel})`;
|
message += ` (${channel})`;
|
||||||
|
@ -75,7 +82,7 @@ class Version extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.version}>
|
<div className={classes.version}>
|
||||||
<Typography className={classes.versionMsg}>{message}</Typography>
|
<Typography className={classes.versionMsg}><Trans>Running</Trans>{message}</Typography>
|
||||||
{this.renderVersionCheck()}
|
{this.renderVersionCheck()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Version from './Version.jsx';
|
import Version from './Version.jsx';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { i18nWrap } from '../../test/testHelpers.jsx';
|
||||||
|
|
||||||
describe('Version', () => {
|
describe('Version', () => {
|
||||||
let curVer = "edge-1.2.3";
|
let curVer = "edge-1.2.3";
|
||||||
let newVer = "edge-2.3.4";
|
let newVer = "edge-2.3.4";
|
||||||
|
|
||||||
it('renders up to date message when versions match', () => {
|
it('renders up to date message when versions match', () => {
|
||||||
const component = mount(
|
const component = mount(i18nWrap(
|
||||||
<Version
|
<Version
|
||||||
classes={{}}
|
classes={{}}
|
||||||
isLatest
|
isLatest
|
||||||
latestVersion={curVer}
|
latestVersion={curVer}
|
||||||
releaseVersion={curVer} />
|
releaseVersion={curVer} />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Linkerd is up to date");
|
expect(component).toIncludeText("Linkerd is up to date");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders update message when versions do not match', () => {
|
it('renders update message when versions do not match', () => {
|
||||||
const component = mount(
|
const component = mount(i18nWrap(
|
||||||
<Version
|
<Version
|
||||||
classes={{}}
|
classes={{}}
|
||||||
isLatest={false}
|
isLatest={false}
|
||||||
latestVersion={newVer}
|
latestVersion={newVer}
|
||||||
releaseVersion={curVer} />
|
releaseVersion={curVer} />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("A new version (2.3.4) is available.");
|
expect(component).toIncludeText("A new version (2.3.4) is available.");
|
||||||
|
@ -33,14 +34,14 @@ describe('Version', () => {
|
||||||
it('renders error when version check fails', () => {
|
it('renders error when version check fails', () => {
|
||||||
let errMsg = "Fake error";
|
let errMsg = "Fake error";
|
||||||
|
|
||||||
const component = mount(
|
const component = mount(i18nWrap(
|
||||||
<Version
|
<Version
|
||||||
classes={{}}
|
classes={{}}
|
||||||
error={{
|
error={{
|
||||||
statusText: errMsg,
|
statusText: errMsg,
|
||||||
}}
|
}}
|
||||||
isLatest={false}
|
isLatest={false}
|
||||||
releaseVersion={curVer} />
|
releaseVersion={curVer} />)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component).toIncludeText("Version check failed: Fake error.");
|
expect(component).toIncludeText("Version check failed: Fake error.");
|
||||||
|
|
|
@ -30,7 +30,7 @@ function SimpleChip(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleChip.propTypes = {
|
SimpleChip.propTypes = {
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.shape({}).isRequired,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TapLink from '../TapLink.jsx';
|
import TapLink from '../TapLink.jsx';
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import _each from 'lodash/each';
|
import _each from 'lodash/each';
|
||||||
import _get from 'lodash/get';
|
import _get from 'lodash/get';
|
||||||
import _has from 'lodash/has';
|
import _has from 'lodash/has';
|
||||||
|
@ -228,9 +229,9 @@ export const processTapEvent = jsonString => {
|
||||||
|
|
||||||
const displayLimit = 3; // how many upstreams/downstreams to display in the popover table
|
const displayLimit = 3; // how many upstreams/downstreams to display in the popover table
|
||||||
const popoverSrcDstColumns = [
|
const popoverSrcDstColumns = [
|
||||||
{ title: 'Source', dataIndex: 'source' },
|
{ title: <Trans>columnTitleSource</Trans>, dataIndex: 'source' },
|
||||||
{ title: '', key: 'arrow', render: () => <FontAwesomeIcon icon={faLongArrowAltRight} /> },
|
{ title: '', key: 'arrow', render: () => <FontAwesomeIcon icon={faLongArrowAltRight} /> },
|
||||||
{ title: 'Destination', dataIndex: 'destination' },
|
{ title: <Trans>columnTitleDestination</Trans>, dataIndex: 'destination' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getPodOwner = (labels, ResourceLink) => {
|
const getPodOwner = (labels, ResourceLink) => {
|
||||||
|
@ -310,8 +311,8 @@ const popoverResourceTable = (d, ResourceLink) => { // eslint-disable-line no-un
|
||||||
};
|
};
|
||||||
|
|
||||||
export const directionColumn = d => (
|
export const directionColumn = d => (
|
||||||
<Tooltip title={d} placement="right">
|
<Tooltip title={d === 'INBOUND' ? <Trans>tooltipInbound</Trans> : <Trans>tooltipOutbound</Trans>} placement="right">
|
||||||
<span>{d === 'INBOUND' ? 'FROM' : 'TO'}</span>
|
<span>{d === 'INBOUND' ? <Trans>columnTitleFrom</Trans> : <Trans>columnTitleTo</Trans>}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,25 @@
|
||||||
{
|
{
|
||||||
"404Msg": "Page not found.",
|
"404Msg": "Page not found.",
|
||||||
|
"A new version ({versionText}) is available.": "A new version ({versionText}) is available.",
|
||||||
"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh",
|
"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh",
|
||||||
"All namespaces have a {productName} install.": "All namespaces have a {productName} install.",
|
"All namespaces have a {productName} install.": "All namespaces have a {productName} install.",
|
||||||
|
"Control plane": "Control plane",
|
||||||
"Control plane components": "Control plane components",
|
"Control plane components": "Control plane components",
|
||||||
|
"Current {cmdNameDisplay} query": "Current {cmdNameDisplay} query",
|
||||||
"Data plane proxies": "Data plane proxies",
|
"Data plane proxies": "Data plane proxies",
|
||||||
|
"LinkerdIsUpToDateMsg": "Linkerd is up to date.",
|
||||||
|
"New service profile": "New service profile",
|
||||||
"NoDataToDisplayMsg": "No data to display",
|
"NoDataToDisplayMsg": "No data to display",
|
||||||
|
"Pod status: {0}": "Pod status: {0}",
|
||||||
|
"Running": "Running",
|
||||||
|
"Service mesh details": "Service mesh details",
|
||||||
|
"Update Now": "Update Now",
|
||||||
|
"Version check failed{0}.": "Version check failed{0}.",
|
||||||
|
"buttonCancel": "Cancel",
|
||||||
|
"buttonClose": "Close",
|
||||||
|
"buttonCreateServiceProfile": "Create Service Profile",
|
||||||
|
"buttonDownload": "Download",
|
||||||
|
"buttonReRunCheck": "Re-Run Check",
|
||||||
"buttonReset": "Reset",
|
"buttonReset": "Reset",
|
||||||
"buttonRunLinkerdCheck": "Run Linkerd Check",
|
"buttonRunLinkerdCheck": "Run Linkerd Check",
|
||||||
"buttonStart": "Start",
|
"buttonStart": "Start",
|
||||||
|
@ -15,7 +30,9 @@
|
||||||
"columnTitleClusterName": "Cluster Name",
|
"columnTitleClusterName": "Cluster Name",
|
||||||
"columnTitleCount": "Count",
|
"columnTitleCount": "Count",
|
||||||
"columnTitleDeployment": "Deployment",
|
"columnTitleDeployment": "Deployment",
|
||||||
|
"columnTitleDestination": "Destination",
|
||||||
"columnTitleDirection": "Direction",
|
"columnTitleDirection": "Direction",
|
||||||
|
"columnTitleFrom": "FROM",
|
||||||
"columnTitleGRPCStatus": "GRPC Status",
|
"columnTitleGRPCStatus": "GRPC Status",
|
||||||
"columnTitleGrafana": "Grafana",
|
"columnTitleGrafana": "Grafana",
|
||||||
"columnTitleHTTPStatus": "HTTP Status",
|
"columnTitleHTTPStatus": "HTTP Status",
|
||||||
|
@ -30,6 +47,7 @@
|
||||||
"columnTitleMethod": "Method",
|
"columnTitleMethod": "Method",
|
||||||
"columnTitleName": "Name",
|
"columnTitleName": "Name",
|
||||||
"columnTitleNamespace": "Namespace",
|
"columnTitleNamespace": "Namespace",
|
||||||
|
"columnTitleNoTraffic": "No Traffic",
|
||||||
"columnTitleOpenConnections": "Connections",
|
"columnTitleOpenConnections": "Connections",
|
||||||
"columnTitleP50Latency": "P50 Latency",
|
"columnTitleP50Latency": "P50 Latency",
|
||||||
"columnTitleP95Latency": "P95 Latency",
|
"columnTitleP95Latency": "P95 Latency",
|
||||||
|
@ -43,23 +61,51 @@
|
||||||
"columnTitleRoute": "Route",
|
"columnTitleRoute": "Route",
|
||||||
"columnTitleSecured": "Secured",
|
"columnTitleSecured": "Secured",
|
||||||
"columnTitleService": "Service",
|
"columnTitleService": "Service",
|
||||||
|
"columnTitleSource": "Source",
|
||||||
"columnTitleSuccessRate": "Success Rate",
|
"columnTitleSuccessRate": "Success Rate",
|
||||||
"columnTitleTap": "Tap",
|
"columnTitleTap": "Tap",
|
||||||
|
"columnTitleTo": "TO",
|
||||||
|
"columnTitleUnmeshed": "Unmeshed",
|
||||||
"columnTitleValue": "Value",
|
"columnTitleValue": "Value",
|
||||||
"columnTitleWeight": "Weight",
|
"columnTitleWeight": "Weight",
|
||||||
"columnTitleWorst": "Worst",
|
"columnTitleWorst": "Worst",
|
||||||
"columnTitleWriteRate": "Write Bytes / sec",
|
"columnTitleWriteRate": "Write Bytes / sec",
|
||||||
"componentsMsg": "Components",
|
"componentsMsg": "Components",
|
||||||
|
"connectResourceMsg {resource}": "Connect your first {resource}",
|
||||||
|
"controllerInstalledMsg": "Controller successfully installed",
|
||||||
|
"createNewProfileMsg": "You can also create a new profile",
|
||||||
"formAuthority": "Authority",
|
"formAuthority": "Authority",
|
||||||
|
"formAuthorityHelpText": "Display requests with this :authority",
|
||||||
|
"formCreateServiceProfileHelpText": "To create a service profile, download a profile and then apply it with `kubectl apply`.",
|
||||||
|
"formGRPCStatus": "GRPC Status",
|
||||||
"formHTTPMethod": "HTTP Method",
|
"formHTTPMethod": "HTTP Method",
|
||||||
|
"formHTTPMethodHelpText": "Display requests with this HTTP method",
|
||||||
|
"formHTTPStatus": "HTTP Status",
|
||||||
|
"formHeaders": "Headers",
|
||||||
"formHideFilters": "Hide filters",
|
"formHideFilters": "Hide filters",
|
||||||
|
"formLatency": "Latency",
|
||||||
"formMaxRPS": "Max RPS",
|
"formMaxRPS": "Max RPS",
|
||||||
|
"formMaxRPSHelpText {defaultMaxRps}": "Maximum requests per second to tap. Default {defaultMaxRps}",
|
||||||
|
"formMethod": "Method",
|
||||||
"formNamespace": "Namespace",
|
"formNamespace": "Namespace",
|
||||||
|
"formNamespaceErrorText": "Namespace must consist of lower case alphanumeric characters or '-' and must start and end with an alphanumeric character",
|
||||||
|
"formNamespaceHelpText": "Namespace to query",
|
||||||
|
"formNoNamedRouteTrafficFound": "No named route traffic found. This could be because the service is not receiving any traffic, or because there is no service profile configured. Does the service have a service profile?",
|
||||||
"formPath": "Path",
|
"formPath": "Path",
|
||||||
|
"formPathHelpText": "Display requests with paths that start with this prefix",
|
||||||
|
"formResource": "Resource",
|
||||||
|
"formResourceHelpText": "Resource to query",
|
||||||
|
"formResponseLengthB": "Response Length (B)",
|
||||||
"formScheme": "Scheme",
|
"formScheme": "Scheme",
|
||||||
|
"formSchemeHelpText": "Display requests with this scheme",
|
||||||
|
"formServiceNameErrorText": "Service name must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character",
|
||||||
"formShowFilters": "Show more filters",
|
"formShowFilters": "Show more filters",
|
||||||
"formToNamespace": "To Namespace",
|
"formToNamespace": "To Namespace",
|
||||||
|
"formToNamespaceHelpText": "Namespace of target resource",
|
||||||
"formToResource": "To Resource",
|
"formToResource": "To Resource",
|
||||||
|
"formToResourceHelpText": "Target resource",
|
||||||
|
"labelError": "Error",
|
||||||
|
"labelSuccess": "Success",
|
||||||
"menuItemCommunity": "Community",
|
"menuItemCommunity": "Community",
|
||||||
"menuItemControlPlane": "Control Plane",
|
"menuItemControlPlane": "Control Plane",
|
||||||
"menuItemCronJobs": "Cron Jobs",
|
"menuItemCronJobs": "Cron Jobs",
|
||||||
|
@ -80,12 +126,20 @@
|
||||||
"menuItemTap": "Tap",
|
"menuItemTap": "Tap",
|
||||||
"menuItemTop": "Top",
|
"menuItemTop": "Top",
|
||||||
"menuItemTrafficSplits": "Traffic Splits",
|
"menuItemTrafficSplits": "Traffic Splits",
|
||||||
|
"noNamespacesDetectedMsg": "No namespaces detected.",
|
||||||
"noResourcesDetectedMsg": "No resources detected.",
|
"noResourcesDetectedMsg": "No resources detected.",
|
||||||
"podsAreInitializingMsg": "Pods are initializing",
|
"podsAreInitializingMsg": "Pods are initializing",
|
||||||
|
"serviceMeshInstalledMsg": "The service mesh was successfully installed!",
|
||||||
"sidebarHeadingCluster": "Cluster",
|
"sidebarHeadingCluster": "Cluster",
|
||||||
"sidebarHeadingConfiguration": "Configuration",
|
"sidebarHeadingConfiguration": "Configuration",
|
||||||
"sidebarHeadingTools": "Tools",
|
"sidebarHeadingTools": "Tools",
|
||||||
"sidebarHeadingWorkloads": "Workloads",
|
"sidebarHeadingWorkloads": "Workloads",
|
||||||
|
"statusExplanationGood": "is up and running",
|
||||||
|
"statusExplanationInMesh": "Added to mesh",
|
||||||
|
"statusExplanationNotInMesh": "Not in mesh",
|
||||||
|
"statusExplanationNotStarted": "has not been started",
|
||||||
|
"tabLiveCalls": "Live Calls",
|
||||||
|
"tabRouteMetrics": "Route Metrics",
|
||||||
"tableTitleEdgesEmpty": "Edges",
|
"tableTitleEdgesEmpty": "Edges",
|
||||||
"tableTitleEdgesWithIdentity {identity}": "Edges (Identity: {identity})",
|
"tableTitleEdgesWithIdentity {identity}": "Edges (Identity: {identity})",
|
||||||
"tableTitleGateways": "Gateways",
|
"tableTitleGateways": "Gateways",
|
||||||
|
@ -94,11 +148,17 @@
|
||||||
"tableTitleLeafServices": "Leaf Services",
|
"tableTitleLeafServices": "Leaf Services",
|
||||||
"tableTitleOutbound": "Outbound",
|
"tableTitleOutbound": "Outbound",
|
||||||
"tableTitlePods": "Pods",
|
"tableTitlePods": "Pods",
|
||||||
|
"tableTitleRequestDetails": "Request Details",
|
||||||
|
"tableTitleRequestInit": "Request Init",
|
||||||
|
"tableTitleResponseEnd": "Response End",
|
||||||
|
"tableTitleResponseInit": "Response Init",
|
||||||
"tableTitleTCP": "TCP",
|
"tableTitleTCP": "TCP",
|
||||||
"tableTitleTCPMetrics": "TCP Metrics",
|
"tableTitleTCPMetrics": "TCP Metrics",
|
||||||
|
"tooltipInbound": "INBOUND",
|
||||||
|
"tooltipOutbound": "OUTBOUND",
|
||||||
"unspecifiedResourcesMsg": "one or more resources",
|
"unspecifiedResourcesMsg": "one or more resources",
|
||||||
"{numUnadded} namespace has no meshed resources.": "{numUnadded} namespace has no meshed resources.",
|
"{numResources, plural, zero {{resource}} one {{resource}} other {{resource}}}": "{numResources, plural, zero {No {resource}s detected} one {# {resource} detected} other {# {resource}s detected}}",
|
||||||
"{numUnadded} namespaces have no meshed resources.": "{numUnadded} namespaces have no meshed resourcesß",
|
"{numUnadded, plural, one {# namespace has no meshed resources} other {# namespaces have no meshed resources}}": "{numUnadded, plural, one {# namespace has no meshed resources.} other {# namespaces have no meshed resources.}}",
|
||||||
"{productName} namespace": "{productName} namespace",
|
"{productName} namespace": "{productName} namespace",
|
||||||
"{productName} version": "{productName} version"
|
"{productName} version": "{productName} version"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,25 @@
|
||||||
{
|
{
|
||||||
"404Msg": "Página no encontrada.",
|
"404Msg": "Página no encontrada.",
|
||||||
|
"A new version ({versionText}) is available.": "Una nueva version ({versionText}) está disponible",
|
||||||
"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Agrega {0} al archivo k8s.yml<0/><1/>Luego ejecuta {inject} para inyectarlo en la malla de servicios",
|
"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Agrega {0} al archivo k8s.yml<0/><1/>Luego ejecuta {inject} para inyectarlo en la malla de servicios",
|
||||||
"All namespaces have a {productName} install.": "Todos los namespaces tienen una instalación de {productName}.",
|
"All namespaces have a {productName} install.": "Todos los namespaces tienen una instalación de {productName}.",
|
||||||
|
"Control plane": "Plano de control",
|
||||||
"Control plane components": "Componentes del plano de control",
|
"Control plane components": "Componentes del plano de control",
|
||||||
|
"Current {cmdNameDisplay} query": "Consulta {cmdNameDisplay} actual",
|
||||||
"Data plane proxies": "Proxies del plano de datos",
|
"Data plane proxies": "Proxies del plano de datos",
|
||||||
|
"LinkerdIsUpToDateMsg": "Linkerd está actualizado.",
|
||||||
|
"New service profile": "Nuevo service profile",
|
||||||
"NoDataToDisplayMsg": "No hay datos que mostrar",
|
"NoDataToDisplayMsg": "No hay datos que mostrar",
|
||||||
|
"Pod status: {0}": "Estado pod: {0}",
|
||||||
|
"Running": "Ejecutando",
|
||||||
|
"Service mesh details": "Detalles de la malla de servicios",
|
||||||
|
"Update Now": "Actualizar Ahora",
|
||||||
|
"Version check failed{0}.": "Error al comprobar la versión{0}",
|
||||||
|
"buttonCancel": "Cancelar",
|
||||||
|
"buttonClose": "Cerrar",
|
||||||
|
"buttonCreateServiceProfile": "Crear Service Profile",
|
||||||
|
"buttonDownload": "Descargar",
|
||||||
|
"buttonReRunCheck": "Volver a Check",
|
||||||
"buttonReset": "Restablecer",
|
"buttonReset": "Restablecer",
|
||||||
"buttonRunLinkerdCheck": "Ejecutar Linkerd Check",
|
"buttonRunLinkerdCheck": "Ejecutar Linkerd Check",
|
||||||
"buttonStart": "Empezar",
|
"buttonStart": "Empezar",
|
||||||
|
@ -15,7 +30,9 @@
|
||||||
"columnTitleClusterName": "Nombre del Clúster",
|
"columnTitleClusterName": "Nombre del Clúster",
|
||||||
"columnTitleCount": "Total",
|
"columnTitleCount": "Total",
|
||||||
"columnTitleDeployment": "Deployment",
|
"columnTitleDeployment": "Deployment",
|
||||||
|
"columnTitleDestination": "Destino",
|
||||||
"columnTitleDirection": "Dirección",
|
"columnTitleDirection": "Dirección",
|
||||||
|
"columnTitleFrom": "DESDE",
|
||||||
"columnTitleGRPCStatus": "Estado GRPC",
|
"columnTitleGRPCStatus": "Estado GRPC",
|
||||||
"columnTitleGrafana": "Grafana",
|
"columnTitleGrafana": "Grafana",
|
||||||
"columnTitleHTTPStatus": "Estado HTTP",
|
"columnTitleHTTPStatus": "Estado HTTP",
|
||||||
|
@ -30,6 +47,7 @@
|
||||||
"columnTitleMethod": "Método",
|
"columnTitleMethod": "Método",
|
||||||
"columnTitleName": "Nombre",
|
"columnTitleName": "Nombre",
|
||||||
"columnTitleNamespace": "Namespace",
|
"columnTitleNamespace": "Namespace",
|
||||||
|
"columnTitleNoTraffic": "No Hay Tráfico",
|
||||||
"columnTitleOpenConnections": "Conexiones",
|
"columnTitleOpenConnections": "Conexiones",
|
||||||
"columnTitleP50Latency": "Latencia P50",
|
"columnTitleP50Latency": "Latencia P50",
|
||||||
"columnTitleP95Latency": "Latencia P95",
|
"columnTitleP95Latency": "Latencia P95",
|
||||||
|
@ -43,23 +61,51 @@
|
||||||
"columnTitleRoute": "Ruta",
|
"columnTitleRoute": "Ruta",
|
||||||
"columnTitleSecured": "Asegurado",
|
"columnTitleSecured": "Asegurado",
|
||||||
"columnTitleService": "Servicio",
|
"columnTitleService": "Servicio",
|
||||||
|
"columnTitleSource": "Fuente",
|
||||||
"columnTitleSuccessRate": "Tasa de éxito",
|
"columnTitleSuccessRate": "Tasa de éxito",
|
||||||
"columnTitleTap": "Tap",
|
"columnTitleTap": "Tap",
|
||||||
|
"columnTitleTo": "HACIA",
|
||||||
|
"columnTitleUnmeshed": "Ausente en la malla de servicios",
|
||||||
"columnTitleValue": "Valor",
|
"columnTitleValue": "Valor",
|
||||||
"columnTitleWeight": "Peso",
|
"columnTitleWeight": "Peso",
|
||||||
"columnTitleWorst": "Peor",
|
"columnTitleWorst": "Peor",
|
||||||
"columnTitleWriteRate": "Escritura Bytes / seg",
|
"columnTitleWriteRate": "Escritura Bytes / seg",
|
||||||
"componentsMsg": "Componentes",
|
"componentsMsg": "Componentes",
|
||||||
|
"connectResourceMsg {resource}": "Conecta tu primer {resource}",
|
||||||
|
"controllerInstalledMsg": "Controller instalado correctamente",
|
||||||
|
"createNewProfileMsg": "También puede crear un nuevo perfil",
|
||||||
"formAuthority": "Authority",
|
"formAuthority": "Authority",
|
||||||
|
"formAuthorityHelpText": "Mostrar solicitudes con este :authority",
|
||||||
|
"formCreateServiceProfileHelpText": "Para crear un service profile, descargue un perfil y luego aplíquelo con `kubectl apply`.",
|
||||||
|
"formGRPCStatus": "Estado GRPC",
|
||||||
"formHTTPMethod": "Método HTTP",
|
"formHTTPMethod": "Método HTTP",
|
||||||
|
"formHTTPMethodHelpText": "Mostrar solicitudes con este método HTTP",
|
||||||
|
"formHTTPStatus": "Estado HTTP",
|
||||||
|
"formHeaders": "Encabezados",
|
||||||
"formHideFilters": "Ocultar filtros",
|
"formHideFilters": "Ocultar filtros",
|
||||||
|
"formLatency": "Latencia",
|
||||||
"formMaxRPS": "Max PPS",
|
"formMaxRPS": "Max PPS",
|
||||||
|
"formMaxRPSHelpText {defaultMaxRps}": "Máximo de solicitudes por segundo para tap. Por defecto {defaultMaxRps}",
|
||||||
|
"formMethod": "Método",
|
||||||
"formNamespace": "Namespace",
|
"formNamespace": "Namespace",
|
||||||
|
"formNamespaceErrorText": "El namespace debe constar de caracteres alfanuméricos en minúscula o '-' y debe comenzar y terminar con un carácter alfanumérico",
|
||||||
|
"formNamespaceHelpText": "Namespace para consultar",
|
||||||
|
"formNoNamedRouteTrafficFound": "No se encontró tráfico de ruta con nombre. Esto podría deberse a que el servicio no está recibiendo tráfico o porque no hay ningún service profile configurado. ¿El servicio tiene un service profile?",
|
||||||
"formPath": "Ruta",
|
"formPath": "Ruta",
|
||||||
|
"formPathHelpText": "Mostrar solicitudes con rutas que comienzan con este prefijo",
|
||||||
|
"formResource": "Recurso",
|
||||||
|
"formResourceHelpText": "Recurso para consultar",
|
||||||
|
"formResponseLengthB": "Longitud De Respuesta (B)",
|
||||||
"formScheme": "Esquema",
|
"formScheme": "Esquema",
|
||||||
|
"formSchemeHelpText": "Mostrar solicitudes con este esquema",
|
||||||
|
"formServiceNameErrorText": "El nombre del servicio debe constar de caracteres alfanuméricos en minúscula o '-' comenzar con un carácter alfabético y terminar con un carácter alfanumérico",
|
||||||
"formShowFilters": "Mostrar más filtros",
|
"formShowFilters": "Mostrar más filtros",
|
||||||
"formToNamespace": "Al Namespace",
|
"formToNamespace": "Al Namespace",
|
||||||
|
"formToNamespaceHelpText": "Namespace del recurso de destino",
|
||||||
"formToResource": "Al Recurso",
|
"formToResource": "Al Recurso",
|
||||||
|
"formToResourceHelpText": "Recurso destino",
|
||||||
|
"labelError": "Error",
|
||||||
|
"labelSuccess": "Éxito",
|
||||||
"menuItemCommunity": "Comunidad",
|
"menuItemCommunity": "Comunidad",
|
||||||
"menuItemControlPlane": "Plano de Control",
|
"menuItemControlPlane": "Plano de Control",
|
||||||
"menuItemCronJobs": "Cron Jobs",
|
"menuItemCronJobs": "Cron Jobs",
|
||||||
|
@ -80,12 +126,20 @@
|
||||||
"menuItemTap": "Tap",
|
"menuItemTap": "Tap",
|
||||||
"menuItemTop": "Top",
|
"menuItemTop": "Top",
|
||||||
"menuItemTrafficSplits": "Traffic Splits",
|
"menuItemTrafficSplits": "Traffic Splits",
|
||||||
|
"noNamespacesDetectedMsg": "No se han encontrado namespaces.",
|
||||||
"noResourcesDetectedMsg": "No se han encontrado recursos.",
|
"noResourcesDetectedMsg": "No se han encontrado recursos.",
|
||||||
"podsAreInitializingMsg": "Los pods se están inicializando",
|
"podsAreInitializingMsg": "Los pods se están inicializando",
|
||||||
|
"serviceMeshInstalledMsg": "¡La malla de servicios se instaló correctamente!",
|
||||||
"sidebarHeadingCluster": "Clúster",
|
"sidebarHeadingCluster": "Clúster",
|
||||||
"sidebarHeadingConfiguration": "Configuración",
|
"sidebarHeadingConfiguration": "Configuración",
|
||||||
"sidebarHeadingTools": "Herramientas",
|
"sidebarHeadingTools": "Herramientas",
|
||||||
"sidebarHeadingWorkloads": "Cargas de trabajo",
|
"sidebarHeadingWorkloads": "Cargas de trabajo",
|
||||||
|
"statusExplanationGood": "está en funcionamiento",
|
||||||
|
"statusExplanationInMesh": "Añadido a la malla",
|
||||||
|
"statusExplanationNotInMesh": "No en malla",
|
||||||
|
"statusExplanationNotStarted": "no se ha iniciado",
|
||||||
|
"tabLiveCalls": "Llamadas En Vivo",
|
||||||
|
"tabRouteMetrics": "Métricas De Ruta",
|
||||||
"tableTitleEdgesEmpty": "Bordes",
|
"tableTitleEdgesEmpty": "Bordes",
|
||||||
"tableTitleEdgesWithIdentity {identity}": "Bordes (Identidad: {identity})",
|
"tableTitleEdgesWithIdentity {identity}": "Bordes (Identidad: {identity})",
|
||||||
"tableTitleGateways": "Gateways",
|
"tableTitleGateways": "Gateways",
|
||||||
|
@ -94,11 +148,17 @@
|
||||||
"tableTitleLeafServices": "Servicios Hoja",
|
"tableTitleLeafServices": "Servicios Hoja",
|
||||||
"tableTitleOutbound": "Salida",
|
"tableTitleOutbound": "Salida",
|
||||||
"tableTitlePods": "Pods",
|
"tableTitlePods": "Pods",
|
||||||
|
"tableTitleRequestDetails": "Detalle Solicitudes",
|
||||||
|
"tableTitleRequestInit": "Solicitud Inicial",
|
||||||
|
"tableTitleResponseEnd": "Fin De Respuesta",
|
||||||
|
"tableTitleResponseInit": "Respuesta Inicial",
|
||||||
"tableTitleTCP": "TCP",
|
"tableTitleTCP": "TCP",
|
||||||
"tableTitleTCPMetrics": "Métricas TCP",
|
"tableTitleTCPMetrics": "Métricas TCP",
|
||||||
|
"tooltipInbound": "ENTRADA",
|
||||||
|
"tooltipOutbound": "SALIDA",
|
||||||
"unspecifiedResourcesMsg": "uno o más recursos",
|
"unspecifiedResourcesMsg": "uno o más recursos",
|
||||||
"{numUnadded} namespace has no meshed resources.": "El namespace {numUnadded} no tiene recursos en la malla de servicios.",
|
"{numResources, plural, zero {{resource}} one {{resource}} other {{resource}}}": "{numResources, plural, zero {No {resource}s detectados} one {# {resource} detectado} other {# {resource}s detectados}}",
|
||||||
"{numUnadded} namespaces have no meshed resources.": "Los namespaces {numUnadded} no tienen recursos en la malla de servicios.",
|
"{numUnadded, plural, one {# namespace has no meshed resources} other {# namespaces have no meshed resources}}": "{numUnadded, plural, one {# namespace no tiene recursos en la malla de servicios.} other {# namespaces no tienen recursos en la malla de servicios.}}",
|
||||||
"{productName} namespace": "Namespace de {productName}",
|
"{productName} namespace": "Namespace de {productName}",
|
||||||
"{productName} version": "Versión de {productName}"
|
"{productName} version": "Versión de {productName}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import ApiHelpers from '../js/components/util/ApiHelpers.jsx';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, Router } from 'react-router';
|
import { Route, Router } from 'react-router';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
|
import catalogEn from './../js/locales/en/messages.js';
|
||||||
|
|
||||||
const componentDefaultProps = {
|
const componentDefaultProps = {
|
||||||
api: ApiHelpers(''),
|
api: ApiHelpers(''),
|
||||||
|
@ -11,6 +13,9 @@ const componentDefaultProps = {
|
||||||
releaseVersion: ''
|
releaseVersion: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectedLocale = 'en';
|
||||||
|
const selectedCatalog = catalogEn;
|
||||||
|
|
||||||
export function routerWrap(Component, extraProps={}, route="/", currentLoc="/") {
|
export function routerWrap(Component, extraProps={}, route="/", currentLoc="/") {
|
||||||
const createElement = (ComponentToWrap, props) => (
|
const createElement = (ComponentToWrap, props) => (
|
||||||
<ComponentToWrap {...(_merge({}, componentDefaultProps, props, extraProps))} />
|
<ComponentToWrap {...(_merge({}, componentDefaultProps, props, extraProps))} />
|
||||||
|
@ -21,3 +26,13 @@ export function routerWrap(Component, extraProps={}, route="/", currentLoc="/")
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function i18nWrap(Component) {
|
||||||
|
return (
|
||||||
|
<I18nProvider
|
||||||
|
language={selectedLocale}
|
||||||
|
catalogs={{ [selectedLocale]: selectedCatalog }}>
|
||||||
|
{Component}
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue