Merge pull request #31674 from its-mitesh-kumar/feat/kubernetes-localization

fix(kubernetes): add missing i18n support for kubernetes and kubernetes-react plugin
This commit is contained in:
Ben Lambert
2025-11-25 10:42:46 +01:00
committed by GitHub
35 changed files with 680 additions and 266 deletions
@@ -9,7 +9,84 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha';
export const kubernetesReactTranslationRef: TranslationRef<
'kubernetes-react',
{
readonly 'events.noEventsFound': 'No events found';
readonly 'events.eventTooltip': '{{eventType}} event';
readonly 'events.firstEvent': 'First event {{timeAgo}} (count: {{count}})';
readonly 'namespace.label': 'namespace:';
readonly 'namespace.labelWithValue': 'namespace: {{namespace}}';
readonly 'cluster.label': 'Cluster';
readonly 'cluster.pods': 'pods';
readonly 'cluster.pods_one': '{{count}} pod';
readonly 'cluster.pods_other': '{{count}} pods';
readonly 'cluster.podsWithErrors': 'pods with errors';
readonly 'cluster.podsWithErrors_one': '{{count}} pod with errors';
readonly 'cluster.podsWithErrors_other': '{{count}} pods with errors';
readonly 'cluster.noPodsWithErrors': 'No pods with errors';
readonly 'errorPanel.message': 'There was a problem retrieving some Kubernetes resources for the entity: {{entityName}}. This could mean that the Error Reporting card is not completely accurate.';
readonly 'errorPanel.title': 'There was a problem retrieving Kubernetes objects';
readonly 'errorPanel.errorsLabel': 'Errors';
readonly 'errorPanel.clusterLabel': 'Cluster';
readonly 'errorPanel.clusterLabelValue': 'Cluster: {{cluster}}';
readonly 'errorPanel.fetchError': 'Error communicating with Kubernetes: {{errorType}}, message: {{message}}';
readonly 'errorPanel.resourceError': "Error fetching Kubernetes resource: '{{resourcePath}}', error: {{errorType}}, status code: {{statusCode}}";
readonly 'pods.pods_one': '{{count}} pod';
readonly 'pods.pods_other': '{{count}} pods';
readonly 'podsTable.columns.name': 'name';
readonly 'podsTable.columns.id': 'ID';
readonly 'podsTable.columns.status': 'status';
readonly 'podsTable.columns.phase': 'phase';
readonly 'podsTable.columns.containersReady': 'containers ready';
readonly 'podsTable.columns.totalRestarts': 'total restarts';
readonly 'podsTable.columns.cpuUsage': 'CPU usage %';
readonly 'podsTable.columns.memoryUsage': 'Memory usage %';
readonly 'podsTable.unknown': 'unknown';
readonly 'podsTable.status.running': 'Running';
readonly 'podsTable.status.ok': 'OK';
readonly 'fixDialog.title': '{{podName}} - {{errorType}}';
readonly 'fixDialog.events': 'Events:';
readonly 'fixDialog.helpButton': 'Help';
readonly 'fixDialog.detectedError': 'Detected error:';
readonly 'fixDialog.causeExplanation': 'Cause explanation:';
readonly 'fixDialog.fix': 'Fix:';
readonly 'fixDialog.crashLogs': 'Crash logs:';
readonly 'fixDialog.openDocs': 'Open docs';
readonly 'fixDialog.ariaLabels.close': 'close';
readonly 'fixDialog.ariaLabels.fixIssue': 'fix issue';
readonly 'podDrawer.buttons.delete': 'Delete Pod';
readonly 'podDrawer.cpuRequests': 'CPU requests';
readonly 'podDrawer.cpuLimits': 'CPU limits';
readonly 'podDrawer.memoryRequests': 'Memory requests';
readonly 'podDrawer.memoryLimits': 'Memory limits';
readonly 'podDrawer.resourceUtilization': 'Resource utilization';
readonly 'hpa.minReplicas': 'min replicas';
readonly 'hpa.maxReplicas': 'max replicas';
readonly 'hpa.replicasSummary': 'min replicas {{min}} / max replicas {{max}}';
readonly 'hpa.currentCpuUsage': 'current CPU usage:';
readonly 'hpa.currentCpuUsageLabel': 'current CPU usage: {{value}}%';
readonly 'hpa.targetCpuUsage': 'target CPU usage:';
readonly 'hpa.targetCpuUsageLabel': 'target CPU usage: {{value}}%';
readonly 'errorReporting.columns.name': 'name';
readonly 'errorReporting.columns.kind': 'kind';
readonly 'errorReporting.columns.messages': 'messages';
readonly 'errorReporting.columns.namespace': 'namespace';
readonly 'errorReporting.columns.cluster': 'cluster';
readonly 'errorReporting.title': 'Error Reporting';
readonly 'podLogs.title': 'No logs emitted';
readonly 'podLogs.description': 'No logs were emitted by the container';
readonly 'podLogs.buttonText': 'Logs';
readonly 'podLogs.titleTemplate': '{{podName}} - {{containerName}} logs on cluster {{clusterName}}';
readonly 'podLogs.buttonAriaLabel': 'get logs';
readonly 'podExecTerminal.buttonText': 'Terminal';
readonly 'podExecTerminal.titleTemplate': '{{podName}} - {{containerName}} terminal shell on cluster {{clusterName}}';
readonly 'podExecTerminal.buttonAriaLabel': 'open terminal';
readonly 'kubernetesDrawer.yaml': 'YAML';
readonly 'kubernetesDrawer.closeDrawer': 'Close the drawer';
readonly 'kubernetesDrawer.managedFields': 'Managed Fields';
readonly 'kubernetesDrawer.unknownName': 'unknown name';
readonly 'linkErrorPanel.message': "Could not format the link to the dashboard of your cluster named '{{clusterName}}'. Its dashboardApp property has been set to '{{dashboardApp}}.'";
readonly 'linkErrorPanel.title': 'There was a problem formatting the link to the Kubernetes dashboard';
readonly 'linkErrorPanel.errorsLabel': 'Errors:';
readonly 'kubernetesDialog.closeAriaLabel': 'close';
}
>;
@@ -42,6 +42,8 @@ import {
import { StatusError, StatusOK } from '@backstage/core-components';
import { PodMetricsContext } from '../../hooks/usePodMetrics';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
type ClusterSummaryProps = {
clusterName: string;
@@ -55,6 +57,7 @@ const ClusterSummary = ({
totalNumberOfPods,
numberOfPodsWithErrors,
}: ClusterSummaryProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
return (
<Grid
container
@@ -75,7 +78,7 @@ const ClusterSummary = ({
<Grid item xs>
<Typography variant="body1">{clusterName}</Typography>
<Typography color="textSecondary" variant="subtitle1">
Cluster
{t('cluster.label')}
</Typography>
</Grid>
</Grid>
@@ -89,13 +92,15 @@ const ClusterSummary = ({
spacing={0}
>
<Grid item>
<StatusOK>{totalNumberOfPods} pods</StatusOK>
<StatusOK>{t('cluster.pods', { count: totalNumberOfPods })}</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>{numberOfPodsWithErrors} pods with errors</StatusError>
<StatusError>
{t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })}
</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
<StatusOK>{t('cluster.noPodsWithErrors')}</StatusOK>
)}
</Grid>
</Grid>
@@ -18,6 +18,8 @@ import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import type { V1ConfigMap } from '@kubernetes/client-node';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const ConfigmapsDrawer = ({
configmap,
@@ -26,6 +28,7 @@ export const ConfigmapsDrawer = ({
configmap: V1ConfigMap;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = configmap.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -55,7 +58,10 @@ export const ConfigmapsDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip
size="small"
label={t('namespace.labelWithValue', { namespace })}
/>
</Grid>
)}
</Grid>
@@ -18,6 +18,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const CronJobDrawer = ({
cronJob,
@@ -26,6 +28,7 @@ export const CronJobDrawer = ({
cronJob: V1CronJob;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = cronJob.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -59,7 +62,10 @@ export const CronJobDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip
size="small"
label={t('namespace.labelWithValue', { namespace })}
/>
</Grid>
)}
</Grid>
@@ -39,6 +39,8 @@ import {
} from '../../../utils/owner';
import { StatusError, StatusOK } from '@backstage/core-components';
import { READY_COLUMNS, RESOURCE_COLUMNS } from '../../Pods/PodsTable';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
type RolloutAccordionsProps = {
rollouts: any[];
@@ -89,6 +91,7 @@ const RolloutSummary = ({
numberOfPodsWithErrors,
hpa,
}: RolloutSummaryProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const pauseTime: string | undefined = rollout.status?.pauseConditions?.find(
(p: any) => p.reason === 'CanaryPauseStep',
)?.startTime;
@@ -125,18 +128,24 @@ const RolloutSummary = ({
>
<Grid item>
<Typography variant="subtitle2">
min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '}
{hpa.spec?.maxReplicas ?? '?'}
{t('hpa.replicasSummary', {
min: String(hpa.spec?.minReplicas ?? '?'),
max: String(hpa.spec?.maxReplicas ?? '?'),
})}
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle2">
current CPU usage: {cpuUtil ?? '?'}%
{t('hpa.currentCpuUsageLabel', {
value: String(cpuUtil ?? '?'),
})}
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle2">
target CPU usage: {specCpuUtil ?? '?'}%
{t('hpa.targetCpuUsageLabel', {
value: String(specCpuUtil ?? '?'),
})}
</Typography>
</Grid>
</Grid>
@@ -153,16 +162,15 @@ const RolloutSummary = ({
spacing={0}
>
<Grid item>
<StatusOK>{numberOfCurrentPods} pods</StatusOK>
<StatusOK>{t('pods.pods', { count: numberOfCurrentPods })}</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>
{numberOfPodsWithErrors} pod
{numberOfPodsWithErrors > 1 ? 's' : ''} with errors
{t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })}
</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
<StatusOK>{t('cluster.noPodsWithErrors')}</StatusOK>
)}
</Grid>
</Grid>
@@ -18,6 +18,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../../KubernetesDrawer'
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
export const RolloutDrawer = ({
rollout,
@@ -26,6 +28,7 @@ export const RolloutDrawer = ({
rollout: any;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = rollout.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -53,7 +56,10 @@ export const RolloutDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip
size="small"
label={t('namespace.labelWithValue', { namespace })}
/>
</Grid>
)}
</Grid>
@@ -30,6 +30,8 @@ import {
} from '../../hooks';
import { StatusError, StatusOK } from '@backstage/core-components';
import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
type DaemonSetsAccordionsProps = {
children?: ReactNode;
@@ -53,6 +55,7 @@ const DaemonSetSummary = ({
numberOfCurrentPods,
numberOfPodsWithErrors,
}: DaemonSetSummaryProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
return (
<Grid
container
@@ -74,16 +77,15 @@ const DaemonSetSummary = ({
spacing={0}
>
<Grid item>
<StatusOK>{numberOfCurrentPods} pods</StatusOK>
<StatusOK>{t('pods.pods', { count: numberOfCurrentPods })}</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>
{numberOfPodsWithErrors} pod
{numberOfPodsWithErrors > 1 ? 's' : ''} with errors
{t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })}
</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
<StatusOK>{t('cluster.noPodsWithErrors')}</StatusOK>
)}
</Grid>
</Grid>
@@ -19,6 +19,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const DaemonSetDrawer = ({
daemonset,
@@ -27,6 +29,7 @@ export const DaemonSetDrawer = ({
daemonset: V1DaemonSet;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = daemonset.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -68,7 +71,10 @@ export const DaemonSetDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip
size="small"
label={t('namespace.labelWithValue', { namespace })}
/>
</Grid>
)}
</Grid>
@@ -21,6 +21,8 @@ import { renderCondition } from '../../utils/pod';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const DeploymentDrawer = ({
deployment,
@@ -29,6 +31,7 @@ export const DeploymentDrawer = ({
deployment: V1Deployment;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = deployment.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -71,7 +74,10 @@ export const DeploymentDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip
size="small"
label={t('namespace.labelWithValue', { namespace })}
/>
</Grid>
)}
</Grid>
@@ -39,6 +39,8 @@ import {
} from '../../hooks';
import { StatusError, StatusOK } from '@backstage/core-components';
import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
type DeploymentsAccordionsProps = {
children?: ReactNode;
@@ -65,6 +67,7 @@ const DeploymentSummary = ({
numberOfPodsWithErrors,
hpa,
}: DeploymentSummaryProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const specCpuUtil = hpa?.spec?.metrics?.find(
metric => metric.type === 'Resource' && metric.resource?.name === 'cpu',
)?.resource?.target.averageUtilization;
@@ -97,18 +100,24 @@ const DeploymentSummary = ({
>
<Grid item>
<Typography variant="subtitle2">
min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '}
{hpa.spec?.maxReplicas ?? '?'}
{t('hpa.replicasSummary', {
min: String(hpa.spec?.minReplicas ?? '?'),
max: String(hpa.spec?.maxReplicas ?? '?'),
})}
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle2">
current CPU usage: {cpuUtil ?? '?'}%
{t('hpa.currentCpuUsageLabel', {
value: String(cpuUtil ?? '?'),
})}
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle2">
target CPU usage: {specCpuUtil ?? '?'}%
{t('hpa.targetCpuUsageLabel', {
value: String(specCpuUtil ?? '?'),
})}
</Typography>
</Grid>
</Grid>
@@ -125,16 +134,15 @@ const DeploymentSummary = ({
spacing={0}
>
<Grid item>
<StatusOK>{numberOfCurrentPods} pods</StatusOK>
<StatusOK>{t('pods.pods', { count: numberOfCurrentPods })}</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>
{numberOfPodsWithErrors} pod
{numberOfPodsWithErrors > 1 ? 's' : ''} with errors
{t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })}
</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
<StatusOK>{t('cluster.noPodsWithErrors')}</StatusOK>
)}
</Grid>
</Grid>
@@ -18,22 +18,37 @@ import { ReactNode } from 'react';
import Typography from '@material-ui/core/Typography';
import { ClusterObjects } from '@backstage/plugin-kubernetes-common';
import { WarningPanel } from '@backstage/core-components';
import {
useTranslationRef,
TranslationFunction,
} from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
const clustersWithErrorsToErrorMessage = (
clustersWithErrors: ClusterObjects[],
t: TranslationFunction<typeof kubernetesReactTranslationRef.T>,
): ReactNode => {
return clustersWithErrors.map((c, i) => {
return (
<div key={i}>
<Typography variant="body2">{`Cluster: ${
c.cluster.title || c.cluster.name
}`}</Typography>
<Typography variant="body2">
{t('errorPanel.clusterLabelValue', {
cluster: c.cluster.title || c.cluster.name,
})}
</Typography>
{c.errors.map((e, j) => {
return (
<Typography variant="body2" key={j}>
{e.errorType === 'FETCH_ERROR'
? `Error communicating with Kubernetes: ${e.errorType}, message: ${e.message}`
: `Error fetching Kubernetes resource: '${e.resourcePath}', error: ${e.errorType}, status code: ${e.statusCode}`}
? t('errorPanel.fetchError', {
errorType: e.errorType,
message: e.message,
})
: t('errorPanel.resourceError', {
resourcePath: e.resourcePath ?? '',
errorType: e.errorType,
statusCode: String(e.statusCode ?? ''),
})}
</Typography>
);
})}
@@ -64,16 +79,24 @@ export const ErrorPanel = ({
entityName,
errorMessage,
clustersWithErrors,
}: ErrorPanelProps) => (
<WarningPanel
title="There was a problem retrieving Kubernetes objects"
message={`There was a problem retrieving some Kubernetes resources for the entity: ${entityName}. This could mean that the Error Reporting card is not completely accurate.`}
>
{clustersWithErrors && (
<div>Errors: {clustersWithErrorsToErrorMessage(clustersWithErrors)}</div>
)}
{errorMessage && (
<Typography variant="body2">Errors: {errorMessage}</Typography>
)}
</WarningPanel>
);
}: ErrorPanelProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
return (
<WarningPanel
title={t('errorPanel.title')}
message={t('errorPanel.message', { entityName })}
>
{clustersWithErrors && (
<div>
{t('errorPanel.errorsLabel')}:{' '}
{clustersWithErrorsToErrorMessage(clustersWithErrors, t)}
</div>
)}
{errorMessage && (
<Typography variant="body2">
{t('errorPanel.errorsLabel')}: {errorMessage}
</Typography>
)}
</WarningPanel>
);
};
@@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo } from 'react';
import {
ClusterAttributes,
DetectedError,
DetectedErrorsByCluster,
} from '@backstage/plugin-kubernetes-common';
import { Table, TableColumn } from '@backstage/core-components';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
/**
*
@@ -30,36 +33,6 @@ export type ErrorReportingProps = {
clusters: ClusterAttributes[];
};
const columns: TableColumn<Row>[] = [
{
title: 'cluster',
width: '10%',
render: (row: Row) => row.cluster.title || row.cluster.name,
},
{
title: 'namespace',
width: '10%',
render: (row: Row) => row.error.sourceRef.namespace,
},
{
title: 'kind',
width: '10%',
render: (row: Row) => row.error.sourceRef.kind,
},
{
title: 'name',
width: '30%',
render: (row: Row) => {
return <>{row.error.sourceRef.name} </>;
},
},
{
title: 'messages',
width: '40%',
render: (row: Row) => row.error.message,
},
];
interface Row {
cluster: ClusterAttributes;
error: DetectedError;
@@ -83,6 +56,7 @@ export const ErrorReporting = ({
detectedErrors,
clusters,
}: ErrorReportingProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const errors = Array.from(detectedErrors.entries())
.flatMap(([clusterName, resourceErrors]) => {
return resourceErrors.map(e => ({
@@ -92,11 +66,44 @@ export const ErrorReporting = ({
})
.sort(sortBySeverity);
const columns: TableColumn<Row>[] = useMemo(
() => [
{
title: String(t('errorReporting.columns.cluster')),
width: '10%',
render: (row: Row) => row.cluster.title || row.cluster.name,
},
{
title: String(t('errorReporting.columns.namespace')),
width: '10%',
render: (row: Row) => row.error.sourceRef.namespace,
},
{
title: String(t('errorReporting.columns.kind')),
width: '10%',
render: (row: Row) => row.error.sourceRef.kind,
},
{
title: String(t('errorReporting.columns.name')),
width: '30%',
render: (row: Row) => {
return <>{row.error.sourceRef.name} </>;
},
},
{
title: String(t('errorReporting.columns.messages')),
width: '40%',
render: (row: Row) => row.error.message,
},
],
[t],
);
return (
<>
{errors.length !== 0 && (
<Table
title="Error Reporting"
title={t('errorReporting.title')}
data={errors}
columns={columns}
options={{ paging: true, search: false, emptyRowsWhenPaging: false }}
@@ -19,6 +19,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const IngressDrawer = ({
ingress,
@@ -27,6 +29,7 @@ export const IngressDrawer = ({
ingress: V1Ingress;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = ingress.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -56,7 +59,7 @@ export const IngressDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip size="small" label={`${t('namespace.label')} ${namespace}`} />
</Grid>
)}
</Grid>
@@ -22,6 +22,8 @@ import IconButton from '@material-ui/core/IconButton';
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
import CloseIcon from '@material-ui/icons/Close';
import { ReactNode, useState } from 'react';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -59,6 +61,7 @@ export const KubernetesDialog = ({
title,
}: KubernetesDialogProps) => {
const classes = useStyles();
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const [open, setOpen] = useState(false);
const openDialog = () => {
@@ -80,7 +83,7 @@ export const KubernetesDialog = ({
<DialogTitle id="dialog-title">
{title}
<IconButton
aria-label="close"
aria-label={t('kubernetesDialog.closeAriaLabel')}
className={classes.closeButton}
onClick={closeDialog}
>
@@ -31,6 +31,8 @@ import {
} from '@material-ui/core/styles';
import CloseIcon from '@material-ui/icons/Close';
import { ManifestYaml } from './ManifestYaml';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
const useDrawerContentStyles = makeStyles((_theme: Theme) =>
createStyles({
@@ -72,6 +74,7 @@ const KubernetesDrawerContent = ({
close,
}: KubernetesDrawerContentProps) => {
const classes = useDrawerContentStyles();
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const [isYaml, setIsYaml] = useState<boolean>(false);
return (
@@ -86,7 +89,7 @@ const KubernetesDrawerContent = ({
<Grid item xs={1}>
<IconButton
key="dismiss"
title="Close the drawer"
title={t('kubernetesDrawer.closeDrawer')}
onClick={() => close()}
color="inherit"
>
@@ -104,10 +107,10 @@ const KubernetesDrawerContent = ({
onChange={event => {
setIsYaml(event.target.checked);
}}
name="YAML"
name={t('kubernetesDrawer.yaml')}
/>
}
label="YAML"
label={t('kubernetesDrawer.yaml')}
/>
</Grid>
</Grid>
@@ -38,6 +38,8 @@ import { ManifestYaml } from './ManifestYaml';
import { useApi } from '@backstage/core-plugin-api';
import { kubernetesClusterLinkFormatterApiRef } from '../../api';
import useAsync from 'react-use/esm/useAsync';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
const useDrawerStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -102,20 +104,24 @@ export type LinkErrorPanelProps = {
export const LinkErrorPanel = ({
cluster,
errorMessage,
}: LinkErrorPanelProps) => (
<WarningPanel
title="There was a problem formatting the link to the Kubernetes dashboard"
message={`Could not format the link to the dashboard of your cluster named '${
cluster.name
}'. Its dashboardApp property has been set to '${
cluster.dashboardApp || 'standard'
}.'`}
>
{errorMessage && (
<Typography variant="body2">Errors: {errorMessage}</Typography>
)}
</WarningPanel>
);
}: LinkErrorPanelProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
return (
<WarningPanel
title={t('linkErrorPanel.title')}
message={t('linkErrorPanel.message', {
clusterName: cluster.name,
dashboardApp: cluster.dashboardApp || 'standard',
})}
>
{errorMessage && (
<Typography variant="body2">
{t('linkErrorPanel.errorsLabel')} {errorMessage}
</Typography>
)}
</WarningPanel>
);
};
/**
*
@@ -152,6 +158,7 @@ const KubernetesStructuredMetadataTableDrawerContent = <
renderObject,
kind,
}: KubernetesStructuredMetadataTableDrawerContentProps<T>) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const [isYaml, setIsYaml] = useState<boolean>(false);
const formatter = useApi(kubernetesClusterLinkFormatterApiRef);
@@ -175,13 +182,13 @@ const KubernetesStructuredMetadataTableDrawerContent = <
<Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={11}>
<Typography variant="h5">
{object.metadata?.name ?? 'unknown name'}
{object.metadata?.name ?? t('kubernetesDrawer.unknownName')}
</Typography>
</Grid>
<Grid item xs={1}>
<IconButton
key="dismiss"
title="Close the drawer"
title={t('kubernetesDrawer.closeDrawer')}
onClick={e => toggleDrawer(e, false)}
color="inherit"
>
@@ -201,10 +208,10 @@ const KubernetesStructuredMetadataTableDrawerContent = <
onChange={event => {
setIsYaml(event.target.checked);
}}
name="YAML"
name={t('kubernetesDrawer.yaml')}
/>
}
label="YAML"
label={t('kubernetesDrawer.yaml')}
/>
</Grid>
</Grid>
@@ -18,6 +18,8 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import jsyaml from 'js-yaml';
import { useState } from 'react';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
/**
* Props of ManifestYaml
@@ -34,6 +36,7 @@ export interface ManifestYamlProps {
* @public
*/
export const ManifestYaml = ({ object }: ManifestYamlProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
// Toggle whether the Kubernetes resource managed fields should be shown in
// the YAML display. This toggle is only available when the YAML is being
// shown because managed fields are never visible in the structured display.
@@ -47,10 +50,10 @@ export const ManifestYaml = ({ object }: ManifestYamlProps) => {
onChange={event => {
setManagedFields(event.target.checked);
}}
name="Managed Fields"
name={t('kubernetesDrawer.managedFields')}
/>
}
label="Managed Fields"
label={t('kubernetesDrawer.managedFields')}
/>
<CodeSnippet
language="yaml"
@@ -19,6 +19,8 @@ import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
import { KubernetesDialog } from '../KubernetesDialog';
import { useIsPodExecTerminalSupported } from '../../hooks';
import { PodExecTerminal, PodExecTerminalProps } from './PodExecTerminal';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
/**
* Opens a terminal connected to the given pod's container in a dialog
@@ -27,23 +29,25 @@ import { PodExecTerminal, PodExecTerminalProps } from './PodExecTerminal';
*/
export const PodExecTerminalDialog = (props: PodExecTerminalProps) => {
const { cluster, containerName, podName } = props;
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const isPodExecTerminalSupported = useIsPodExecTerminalSupported();
return (
!isPodExecTerminalSupported.loading &&
isPodExecTerminalSupported.value && (
<KubernetesDialog
buttonAriaLabel="open terminal"
buttonAriaLabel={t('podExecTerminal.buttonAriaLabel')}
buttonIcon={<OpenInBrowserIcon />}
buttonText="Terminal"
buttonText={t('podExecTerminal.buttonText')}
disabled={
isPodExecTerminalSupported.loading ||
!isPodExecTerminalSupported.value
}
title={`${podName} - ${containerName} terminal shell on cluster ${
cluster.title || cluster.name
}`}
title={t('podExecTerminal.titleTemplate', {
podName,
containerName,
clusterName: cluster.title || cluster.name,
})}
>
<PodExecTerminal {...props} />
</KubernetesDialog>
@@ -17,44 +17,57 @@ import { render } from '@testing-library/react';
import { ErrorList } from './ErrorList';
import { Pod } from 'kubernetes-models/v1';
import { TestApiProvider, mockApis } from '@backstage/test-utils';
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
import { errorApiRef } from '@backstage/core-plugin-api';
describe('ErrorList', () => {
const translationApi = mockApis.translation();
const errorApi = { post: jest.fn(), error$: jest.fn() };
it('error highlight should render', () => {
const { getByText } = render(
<ErrorList
podAndErrors={[
{
cluster: { name: 'some-cluster' },
pod: {
metadata: {
name: 'some-pod',
namespace: 'some-namespace',
},
} as Pod,
errors: [
{
type: 'some-error',
severity: 10,
message: 'some error message',
occurrenceCount: 1,
sourceRef: {
<TestApiProvider
apis={[
[translationApiRef, translationApi],
[errorApiRef, errorApi],
]}
>
<ErrorList
podAndErrors={[
{
cluster: { name: 'some-cluster' },
pod: {
metadata: {
name: 'some-pod',
namespace: 'some-namespace',
kind: 'Pod',
apiGroup: 'v1',
},
proposedFix: {
type: 'logs',
container: 'some-container',
errorType: 'some error type',
rootCauseExplanation: 'some root cause',
actions: ['fix1', 'fix2'],
} as Pod,
errors: [
{
type: 'some-error',
severity: 10,
message: 'some error message',
occurrenceCount: 1,
sourceRef: {
name: 'some-pod',
namespace: 'some-namespace',
kind: 'Pod',
apiGroup: 'v1',
},
proposedFix: {
type: 'logs',
container: 'some-container',
errorType: 'some error type',
rootCauseExplanation: 'some root cause',
actions: ['fix1', 'fix2'],
},
},
},
],
},
]}
/>,
],
},
]}
/>
</TestApiProvider>,
);
expect(getByText('some-pod')).toBeInTheDocument();
expect(getByText('some error message')).toBeInTheDocument();
@@ -17,63 +17,25 @@ import { EventsContent } from './Events';
import { render } from '@testing-library/react';
import { Event } from 'kubernetes-models/v1';
import { DateTime } from 'luxon';
import { TestApiProvider, mockApis } from '@backstage/test-utils';
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
import { errorApiRef } from '@backstage/core-plugin-api';
describe('EventsContent', () => {
const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO();
const translationApi = mockApis.translation();
const errorApi = { post: jest.fn(), error$: jest.fn() };
it('should show info events', () => {
const { getByText } = render(
<EventsContent
events={[
{
type: 'Info',
message: 'hello there',
reason: 'something happened',
count: 52,
metadata: {
creationTimestamp: oneHourAgo,
},
} as Event,
<TestApiProvider
apis={[
[translationApiRef, translationApi],
[errorApiRef, errorApi],
]}
/>,
);
expect(getByText('First event 1 hour ago (count: 52)')).toBeInTheDocument();
expect(getByText('something happened: hello there')).toBeInTheDocument();
});
it('should show warning events', () => {
const { getByText } = render(
<EventsContent
events={[
{
type: 'Warning',
message: 'uh oh',
reason: 'something happened',
count: 23,
metadata: {
creationTimestamp: oneHourAgo,
},
} as Event,
]}
/>,
);
expect(getByText('First event 1 hour ago (count: 23)')).toBeInTheDocument();
expect(getByText('something happened: uh oh')).toBeInTheDocument();
});
it('should only show warning events when warningEventsOnly set', () => {
const { getByText, queryByText } = render(
<EventsContent
warningEventsOnly
events={
[
{
type: 'Warning',
message: 'uh oh',
reason: 'something happened',
count: 23,
metadata: {
creationTimestamp: oneHourAgo,
},
},
>
<EventsContent
events={[
{
type: 'Info',
message: 'hello there',
@@ -82,10 +44,75 @@ describe('EventsContent', () => {
metadata: {
creationTimestamp: oneHourAgo,
},
},
] as Event[]
}
/>,
} as Event,
]}
/>
</TestApiProvider>,
);
expect(getByText('First event 1 hour ago (count: 52)')).toBeInTheDocument();
expect(getByText('something happened: hello there')).toBeInTheDocument();
});
it('should show warning events', () => {
const { getByText } = render(
<TestApiProvider
apis={[
[translationApiRef, translationApi],
[errorApiRef, errorApi],
]}
>
<EventsContent
events={[
{
type: 'Warning',
message: 'uh oh',
reason: 'something happened',
count: 23,
metadata: {
creationTimestamp: oneHourAgo,
},
} as Event,
]}
/>
</TestApiProvider>,
);
expect(getByText('First event 1 hour ago (count: 23)')).toBeInTheDocument();
expect(getByText('something happened: uh oh')).toBeInTheDocument();
});
it('should only show warning events when warningEventsOnly set', () => {
const { getByText, queryByText } = render(
<TestApiProvider
apis={[
[translationApiRef, translationApi],
[errorApiRef, errorApi],
]}
>
<EventsContent
warningEventsOnly
events={
[
{
type: 'Warning',
message: 'uh oh',
reason: 'something happened',
count: 23,
metadata: {
creationTimestamp: oneHourAgo,
},
},
{
type: 'Info',
message: 'hello there',
reason: 'something happened',
count: 52,
metadata: {
creationTimestamp: oneHourAgo,
},
},
] as Event[]
}
/>
</TestApiProvider>,
);
expect(queryByText('First event 1 hour ago (count: 52)')).toBeNull();
expect(queryByText('something happened: hello there')).toBeNull();
@@ -31,6 +31,8 @@ import { useEvents } from './useEvents';
import Skeleton from '@material-ui/lab/Skeleton';
import { DismissableBanner } from '@backstage/core-components';
import { Event } from 'kubernetes-models/v1';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
/**
* Props for Events
@@ -59,8 +61,9 @@ export const EventsContent = ({
events,
warningEventsOnly,
}: EventsContentProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
if (events.length === 0) {
return <Typography>No events found</Typography>;
return <Typography>{t('events.noEventsFound')}</Typography>;
}
return (
@@ -35,6 +35,8 @@ import { Events } from '../Events';
import { LinkButton } from '@backstage/core-components';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -72,6 +74,7 @@ export const FixDialog: FC<FixDialogProps> = ({
}: FixDialogProps) => {
const [isOpen, setOpen] = useState(!!open);
const classes = useStyles();
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const openDialog = () => {
setOpen(true);
@@ -87,21 +90,27 @@ export const FixDialog: FC<FixDialogProps> = ({
return (
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Detected error:</Typography>
<Typography variant="h6">{t('fixDialog.detectedError')}</Typography>
<Typography>{error.message}</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">Cause explanation:</Typography>
<Typography variant="h6">
{t('fixDialog.causeExplanation')}
</Typography>
<Typography>
{error.proposedFix?.rootCauseExplanation ?? 'unknown'}
{error.proposedFix?.rootCauseExplanation ?? t('podsTable.unknown')}
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">Fix:</Typography>
<Typography variant="h6">{t('fixDialog.fix')}</Typography>
<List>
{(error.proposedFix?.actions ?? []).map((fix, i) => {
return (
<ListItem key={`${pod.metadata?.name ?? 'unknown'}-pf-${i}`}>
<ListItem
key={`${
pod.metadata?.name ?? t('podsTable.unknown')
}-pf-${i}`}
>
{fix}
</ListItem>
);
@@ -112,14 +121,15 @@ export const FixDialog: FC<FixDialogProps> = ({
{pf && pf.type === 'logs' && (
<>
<Grid item xs={12}>
<Typography variant="h6">Crash logs:</Typography>
<Typography variant="h6">{t('fixDialog.crashLogs')}</Typography>
</Grid>
<Grid item xs={9}>
<PodLogs
previous
containerScope={{
podName: pod.metadata?.name ?? 'unknown',
podNamespace: pod.metadata?.namespace ?? 'unknown',
podName: pod.metadata?.name ?? t('podsTable.unknown'),
podNamespace:
pod.metadata?.namespace ?? t('podsTable.unknown'),
cluster: { name: clusterName },
containerName: pf.container,
}}
@@ -130,7 +140,7 @@ export const FixDialog: FC<FixDialogProps> = ({
{pf && pf.type === 'events' && (
<>
<Grid item xs={12}>
<Typography variant="h6">Events:</Typography>
<Typography variant="h6">{t('fixDialog.events')}</Typography>
</Grid>
<Grid item xs={9}>
<Events
@@ -150,18 +160,21 @@ export const FixDialog: FC<FixDialogProps> = ({
<>
<Button
variant="outlined"
aria-label="fix issue"
aria-label={t('fixDialog.ariaLabels.fixIssue')}
component="label"
onClick={openDialog}
startIcon={<HelpIcon />}
>
Help
{t('fixDialog.helpButton')}
</Button>
<Dialog maxWidth="xl" fullWidth open={isOpen} onClose={closeDialog}>
<DialogTitle id="dialog-title">
{pod.metadata?.name} - {error.type}
{t('fixDialog.title', {
podName: pod.metadata?.name ?? '',
errorType: error.type,
})}
<IconButton
aria-label="close"
aria-label={t('fixDialog.ariaLabels.close')}
className={classes.closeButton}
onClick={closeDialog}
>
@@ -178,7 +191,7 @@ export const FixDialog: FC<FixDialogProps> = ({
target="_blank"
rel="noopener"
>
Open docs
{t('fixDialog.openDocs')}
</LinkButton>
)}
</DialogActions>
@@ -30,6 +30,8 @@ import { bytesToMiB, formatMillicores } from '../../../utils/resources';
import { PodExecTerminalDialog } from '../../PodExecTerminal/PodExecTerminalDialog';
import { ResourceUtilization } from '../../ResourceUtilization';
import { PodLogsDialog, PodScope } from '../PodLogs';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
const getContainerHealthChecks = (
containerSpec: IContainer,
@@ -112,6 +114,7 @@ export const ContainerCard: FC<ContainerCardProps> = ({
containerStatus,
containerMetrics,
}: ContainerCardProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const isPodExecTerminalEnabled = useIsPodExecTerminalEnabled();
// This should never be undefined
@@ -183,13 +186,13 @@ export const ContainerCard: FC<ContainerCardProps> = ({
<Grid container item xs={12} spacing={0}>
<Grid item xs={12}>
<Typography variant="subtitle1">
Resource utilization
{t('podDrawer.resourceUtilization')}
</Typography>
</Grid>
<Grid item xs={12} style={{ minHeight: '5rem' }}>
<ResourceUtilization
compressed
title="CPU requests"
title={t('podDrawer.cpuRequests')}
usage={containerMetrics.cpuUsage.currentUsage}
total={containerMetrics.cpuUsage.requestTotal}
totalFormatted={formatMillicores(
@@ -198,7 +201,7 @@ export const ContainerCard: FC<ContainerCardProps> = ({
/>
<ResourceUtilization
compressed
title="CPU limits"
title={t('podDrawer.cpuLimits')}
usage={containerMetrics.cpuUsage.currentUsage}
total={containerMetrics.cpuUsage.limitTotal}
totalFormatted={formatMillicores(
@@ -207,7 +210,7 @@ export const ContainerCard: FC<ContainerCardProps> = ({
/>
<ResourceUtilization
compressed
title="Memory requests"
title={t('podDrawer.memoryRequests')}
usage={containerMetrics.memoryUsage.currentUsage}
total={containerMetrics.memoryUsage.requestTotal}
totalFormatted={bytesToMiB(
@@ -216,7 +219,7 @@ export const ContainerCard: FC<ContainerCardProps> = ({
/>
<ResourceUtilization
compressed
title="Memory limits"
title={t('podDrawer.memoryLimits')}
usage={containerMetrics.memoryUsage.currentUsage}
total={containerMetrics.memoryUsage.limitTotal}
totalFormatted={bytesToMiB(
@@ -32,6 +32,8 @@ import { usePodMetrics } from '../../../hooks/usePodMetrics';
import { ResourceUtilization } from '../../ResourceUtilization';
import { bytesToMiB, formatMillicores } from '../../../utils/resources';
import { useIsPodDeleteEnabled } from '../../../hooks';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
const useDrawerContentStyles = makeStyles((_theme: Theme) =>
createStyles({
@@ -77,6 +79,7 @@ export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => {
const classes = useDrawerContentStyles();
const podMetrics = usePodMetrics(podAndErrors.cluster.name, podAndErrors.pod);
const isPodDeleteEnabled = useIsPodDeleteEnabled();
const { t } = useTranslationRef(kubernetesReactTranslationRef);
return (
<KubernetesDrawer
@@ -108,17 +111,19 @@ export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => {
{podMetrics && (
<Grid container item xs={12}>
<Grid item xs={12}>
<Typography variant="h5">Resource utilization</Typography>
<Typography variant="h5">
{t('podDrawer.resourceUtilization')}
</Typography>
</Grid>
<Grid item xs={6}>
<ResourceUtilization
title="CPU requests"
title={t('podDrawer.cpuRequests')}
usage={podMetrics.cpu.currentUsage}
total={podMetrics.cpu.requestTotal}
totalFormatted={formatMillicores(podMetrics.cpu.requestTotal)}
/>
<ResourceUtilization
title="CPU limits"
title={t('podDrawer.cpuLimits')}
usage={podMetrics.cpu.currentUsage}
total={podMetrics.cpu.limitTotal}
totalFormatted={formatMillicores(podMetrics.cpu.limitTotal)}
@@ -126,13 +131,13 @@ export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => {
</Grid>
<Grid item xs={6}>
<ResourceUtilization
title="Memory requests"
title={t('podDrawer.memoryRequests')}
usage={podMetrics.memory.currentUsage}
total={podMetrics.memory.requestTotal}
totalFormatted={bytesToMiB(podMetrics.memory.requestTotal)}
/>
<ResourceUtilization
title="Memory limits"
title={t('podDrawer.memoryLimits')}
usage={podMetrics.memory.currentUsage}
total={podMetrics.memory.limitTotal}
totalFormatted={bytesToMiB(podMetrics.memory.limitTotal)}
@@ -25,6 +25,8 @@ import Skeleton from '@material-ui/lab/Skeleton';
import { ContainerScope } from './types';
import { usePodLogs } from './usePodLogs';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
/**
* Props for PodLogs
@@ -45,6 +47,7 @@ export const PodLogs: FC<PodLogsProps> = ({
containerScope,
previous,
}: PodLogsProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const { value, error, loading } = usePodLogs({
containerScope,
previous,
@@ -72,8 +75,8 @@ export const PodLogs: FC<PodLogsProps> = ({
(value.text === '' ? (
<EmptyState
missing="data"
title="No logs emitted"
description="No logs were emitted by the container"
title={t('podLogs.title')}
description={t('podLogs.description')}
/>
) : (
<LogViewer text={value.text} />
@@ -18,6 +18,8 @@ import SubjectIcon from '@material-ui/icons/Subject';
import { KubernetesDialog } from '../../KubernetesDialog';
import { PodLogs } from './PodLogs';
import { ContainerScope } from './types';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../../translation';
/**
* Props for PodLogsDialog
@@ -34,17 +36,19 @@ export interface PodLogsDialogProps {
* @public
*/
export const PodLogsDialog = ({ containerScope }: PodLogsDialogProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
return (
<KubernetesDialog
buttonAriaLabel="get logs"
buttonAriaLabel={t('podLogs.buttonAriaLabel')}
buttonIcon={<SubjectIcon />}
buttonText="Logs"
buttonText={t('podLogs.buttonText')}
disabled={false}
title={`${containerScope.podName} - ${
containerScope.containerName
} logs on cluster ${
containerScope.cluster.title || containerScope.cluster.name
}`}
title={t('podLogs.titleTemplate', {
podName: containerScope.podName,
containerName: containerScope.containerName,
clusterName:
containerScope.cluster.title || containerScope.cluster.name,
})}
>
<PodLogs containerScope={containerScope} />
</KubernetesDialog>
+31 -25
View File
@@ -30,6 +30,8 @@ import { Pod } from 'kubernetes-models/v1/Pod';
import type { V1Pod } from '@kubernetes/client-node';
import { usePodMetrics } from '../../hooks/usePodMetrics';
import Typography from '@material-ui/core/Typography';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
/**
*
@@ -63,22 +65,6 @@ export type PodsTablesProps = {
children?: ReactNode;
};
const READY: TableColumn<Pod>[] = [
{
title: 'containers ready',
align: 'center',
render: containersReady,
width: 'auto',
},
{
title: 'total restarts',
align: 'center',
render: totalRestarts,
type: 'numeric',
width: 'auto',
},
];
const PodDrawerTrigger = ({ pod }: { pod: Pod }) => {
const errors = useMatchingErrors({
kind: 'Pod',
@@ -98,9 +84,10 @@ const PodDrawerTrigger = ({ pod }: { pod: Pod }) => {
const Cpu = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => {
const metrics = usePodMetrics(clusterName, pod);
const { t } = useTranslationRef(kubernetesReactTranslationRef);
if (!metrics) {
return <Typography>unknown</Typography>;
return <Typography>{t('podsTable.unknown')}</Typography>;
}
return <>{podStatusToCpuUtil(metrics)}</>;
@@ -108,9 +95,10 @@ const Cpu = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => {
const Memory = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => {
const metrics = usePodMetrics(clusterName, pod);
const { t } = useTranslationRef(kubernetesReactTranslationRef);
if (!metrics) {
return <Typography>unknown</Typography>;
return <Typography>{t('podsTable.unknown')}</Typography>;
}
return <>{podStatusToMemoryUtil(metrics)}</>;
@@ -123,26 +111,44 @@ const Memory = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => {
*/
export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => {
const cluster = useContext(ClusterContext);
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const READY: TableColumn<Pod>[] = [
{
title: t('podsTable.columns.containersReady'),
align: 'center',
render: containersReady,
width: 'auto',
},
{
title: t('podsTable.columns.totalRestarts'),
align: 'center',
render: totalRestarts,
type: 'numeric',
width: 'auto',
},
];
const defaultColumns: TableColumn<Pod>[] = [
{
title: 'ID',
title: t('podsTable.columns.id'),
field: 'metadata.uid',
hidden: true,
},
{
title: 'name',
title: t('podsTable.columns.name'),
highlight: true,
render: (pod: Pod) => {
return <PodDrawerTrigger pod={pod} />;
},
},
{
title: 'phase',
render: (pod: Pod) => pod.status?.phase ?? 'unknown',
title: t('podsTable.columns.phase'),
render: (pod: Pod) => pod.status?.phase ?? t('podsTable.unknown'),
width: 'auto',
},
{
title: 'status',
title: t('podsTable.columns.status'),
render: containerStatuses,
},
];
@@ -154,14 +160,14 @@ export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => {
if (extraColumns.includes(RESOURCE_COLUMNS)) {
const resourceColumns: TableColumn<Pod>[] = [
{
title: 'CPU usage %',
title: t('podsTable.columns.cpuUsage'),
render: (pod: Pod) => {
return <Cpu clusterName={cluster.name} pod={pod} />;
},
width: 'auto',
},
{
title: 'Memory usage %',
title: t('podsTable.columns.memoryUsage'),
render: (pod: Pod) => {
return <Memory clusterName={cluster.name} pod={pod} />;
},
@@ -19,6 +19,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const ServiceDrawer = ({
service,
@@ -27,6 +29,7 @@ export const ServiceDrawer = ({
service: V1Service;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = service.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -56,7 +59,7 @@ export const ServiceDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip size="small" label={`${t('namespace.label')} ${namespace}`} />
</Grid>
)}
</Grid>
@@ -21,6 +21,8 @@ import { renderCondition } from '../../utils/pod';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Chip from '@material-ui/core/Chip';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
export const StatefulSetDrawer = ({
statefulset,
@@ -29,6 +31,7 @@ export const StatefulSetDrawer = ({
statefulset: V1StatefulSet;
expanded?: boolean;
}) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const namespace = statefulset.metadata?.namespace;
return (
<KubernetesStructuredMetadataTableDrawer
@@ -72,7 +75,7 @@ export const StatefulSetDrawer = ({
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
<Chip size="small" label={`${t('namespace.label')} ${namespace}`} />
</Grid>
)}
</Grid>
@@ -36,6 +36,8 @@ import {
} from '../../hooks';
import { StatusError, StatusOK } from '@backstage/core-components';
import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '../../translation';
type StatefulSetsAccordionsProps = {
children?: ReactNode;
@@ -62,6 +64,7 @@ const StatefulSetSummary = ({
numberOfPodsWithErrors,
hpa,
}: StatefulSetSummaryProps) => {
const { t } = useTranslationRef(kubernetesReactTranslationRef);
const specCpuUtil = hpa?.spec?.metrics?.find(
metric => metric.type === 'Resource' && metric.resource?.name === 'cpu',
)?.resource?.target.averageUtilization;
@@ -94,18 +97,18 @@ const StatefulSetSummary = ({
>
<Grid item>
<Typography variant="subtitle2">
min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '}
{hpa.spec?.maxReplicas ?? '?'}
{t('hpa.minReplicas')} {hpa.spec?.minReplicas ?? '?'} /{' '}
{t('hpa.maxReplicas')} {hpa.spec?.maxReplicas ?? '?'}
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle2">
current CPU usage: {cpuUtil ?? '?'}%
{t('hpa.currentCpuUsage')} {cpuUtil ?? '?'}%
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle2">
target CPU usage: {specCpuUtil ?? '?'}%
{t('hpa.targetCpuUsage')} {specCpuUtil ?? '?'}%
</Typography>
</Grid>
</Grid>
@@ -122,16 +125,15 @@ const StatefulSetSummary = ({
spacing={0}
>
<Grid item>
<StatusOK>{numberOfCurrentPods} pods</StatusOK>
<StatusOK>{t('pods.pods', { count: numberOfCurrentPods })}</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>
{numberOfPodsWithErrors} pod
{numberOfPodsWithErrors > 1 ? 's' : ''} with errors
{t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })}
</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
<StatusOK>{t('cluster.noPodsWithErrors')}</StatusOK>
)}
</Grid>
</Grid>
+120
View File
@@ -20,10 +20,130 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha';
export const kubernetesReactTranslationRef = createTranslationRef({
id: 'kubernetes-react',
messages: {
cluster: {
label: 'Cluster',
pods: 'pods',
pods_one: '{{count}} pod',
pods_other: '{{count}} pods',
podsWithErrors: 'pods with errors',
podsWithErrors_one: '{{count}} pod with errors',
podsWithErrors_other: '{{count}} pods with errors',
noPodsWithErrors: 'No pods with errors',
},
podsTable: {
columns: {
id: 'ID',
name: 'name',
phase: 'phase',
status: 'status',
containersReady: 'containers ready',
totalRestarts: 'total restarts',
cpuUsage: 'CPU usage %',
memoryUsage: 'Memory usage %',
},
unknown: 'unknown',
status: {
running: 'Running',
ok: 'OK',
},
},
errorPanel: {
title: 'There was a problem retrieving Kubernetes objects',
message:
'There was a problem retrieving some Kubernetes resources for the entity: {{entityName}}. This could mean that the Error Reporting card is not completely accurate.',
errorsLabel: 'Errors',
clusterLabel: 'Cluster',
clusterLabelValue: 'Cluster: {{cluster}}',
fetchError:
'Error communicating with Kubernetes: {{errorType}}, message: {{message}}',
resourceError:
"Error fetching Kubernetes resource: '{{resourcePath}}', error: {{errorType}}, status code: {{statusCode}}",
},
fixDialog: {
helpButton: 'Help',
title: '{{podName}} - {{errorType}}',
detectedError: 'Detected error:',
causeExplanation: 'Cause explanation:',
fix: 'Fix:',
crashLogs: 'Crash logs:',
events: 'Events:',
openDocs: 'Open docs',
ariaLabels: {
fixIssue: 'fix issue',
close: 'close',
},
},
podDrawer: {
buttons: {
delete: 'Delete Pod',
},
cpuRequests: 'CPU requests',
cpuLimits: 'CPU limits',
memoryRequests: 'Memory requests',
memoryLimits: 'Memory limits',
resourceUtilization: 'Resource utilization',
},
hpa: {
minReplicas: 'min replicas',
maxReplicas: 'max replicas',
replicasSummary: 'min replicas {{min}} / max replicas {{max}}',
currentCpuUsage: 'current CPU usage:',
currentCpuUsageLabel: 'current CPU usage: {{value}}%',
targetCpuUsage: 'target CPU usage:',
targetCpuUsageLabel: 'target CPU usage: {{value}}%',
},
pods: {
pods_one: '{{count}} pod',
pods_other: '{{count}} pods',
},
errorReporting: {
title: 'Error Reporting',
columns: {
cluster: 'cluster',
namespace: 'namespace',
kind: 'kind',
name: 'name',
messages: 'messages',
},
},
podLogs: {
title: 'No logs emitted',
description: 'No logs were emitted by the container',
buttonText: 'Logs',
buttonAriaLabel: 'get logs',
titleTemplate:
'{{podName}} - {{containerName}} logs on cluster {{clusterName}}',
},
podExecTerminal: {
buttonText: 'Terminal',
buttonAriaLabel: 'open terminal',
titleTemplate:
'{{podName}} - {{containerName}} terminal shell on cluster {{clusterName}}',
},
events: {
noEventsFound: 'No events found',
eventTooltip: '{{eventType}} event',
firstEvent: 'First event {{timeAgo}} (count: {{count}})',
},
kubernetesDrawer: {
closeDrawer: 'Close the drawer',
yaml: 'YAML',
managedFields: 'Managed Fields',
unknownName: 'unknown name',
},
linkErrorPanel: {
title:
'There was a problem formatting the link to the Kubernetes dashboard',
message:
"Could not format the link to the dashboard of your cluster named '{{clusterName}}'. Its dashboardApp property has been set to '{{dashboardApp}}.'",
errorsLabel: 'Errors:',
},
namespace: {
label: 'namespace:',
labelWithValue: 'namespace: {{namespace}}',
},
kubernetesDialog: {
closeAriaLabel: 'close',
},
},
});