Merge pull request #22497 from jamieklassen/cluster-title

'title' field for Kubernetes clusters
This commit is contained in:
Ben Lambert
2024-02-13 14:30:51 +01:00
committed by GitHub
50 changed files with 386 additions and 52 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-kubernetes-react': minor
---
**BREAKING** The `PodScope`, `PodAndErrors`, and `PodExecTerminalProps` types no
longer have a `clusterName` field; instead they now have the field `cluster`
which contains the full `ClusterAttributes`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-react': patch
---
Pod dialogs display cluster title when specified.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-kubernetes-react': patch
---
The `ErrorPanel` component will display the `title` field (when specified) for
clusters with errors.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
The `/clusters` API now surfaces cluster titles.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-common': patch
---
The `ClusterAttributes` type now includes the cluster title.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-kubernetes': patch
'@backstage/plugin-kubernetes-react': patch
---
The `ErrorReporting` component's cluster column now displays cluster titles when
specified.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Responses from the `/api/kubernetes/services/:serviceId` endpoint now include the cluster title.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-react': patch
---
The `Cluster` component now renders the cluster's title, if specified.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-kubernetes-node': patch
---
The `ClusterDetails` type now has a `title` field, which should be a
human-readable name.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Clusters in the catalog can now specify a human-readable title via `metadata.title`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Clusters in the app-config can now specify a `title` property for human readability.
@@ -92,6 +92,7 @@ Deutsche
dev
devops
devs
dialogs
discoverability
Discoverability
dls
@@ -38,6 +38,7 @@ kubernetes:
plural: 'rollouts'
- url: http://127.0.0.2:9999
name: aws-cluster-1
title: 'My AWS Cluster Number One'
authProvider: 'aws'
- type: 'gke'
projectId: 'gke-clusters'
@@ -151,6 +152,11 @@ The base URL to the Kubernetes control plane. Can be found by using the
A name to represent this cluster, this must be unique within the `clusters`
array. Users will see this value in the Software Catalog Kubernetes plugin.
##### `clusters.\*.title`
A human-readable name for the cluster. This value will override the `name` field
for the purposes of display in the catalog.
##### `clusters.\*.authProvider`
This determines how the Kubernetes client authenticates with the Kubernetes
+2
View File
@@ -43,6 +43,8 @@ export interface Config {
url: string;
/** @visibility frontend */
name: string;
/** @visibility frontend */
title?: string;
/** @visibility secret */
serviceAccountToken?: string;
/** @visibility frontend */
@@ -43,6 +43,7 @@ const mockCatalogApi = {
'kubernetes.io/dashboard-app': 'my-app',
},
name: 'owned',
title: 'title',
namespace: 'default',
},
},
@@ -63,6 +63,7 @@ export class CatalogClusterLocator implements KubernetesClustersSupplier {
const annotations = entity.metadata.annotations!;
const clusterDetails: ClusterDetails = {
name: entity.metadata.name,
title: entity.metadata.title,
url: annotations[ANNOTATION_KUBERNETES_API_SERVER],
authMetadata: annotations,
caData: annotations[ANNOTATION_KUBERNETES_API_SERVER_CA],
@@ -21,8 +21,8 @@ import {
ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE,
ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID,
} from '@backstage/plugin-kubernetes-common';
import { ClusterDetails } from '@backstage/plugin-kubernetes-node';
import { ConfigClusterLocator } from './ConfigClusterLocator';
import { ClusterDetails } from '../types/types';
import { AuthenticationStrategy } from '../auth';
describe('ConfigClusterLocator', () => {
@@ -78,6 +78,28 @@ describe('ConfigClusterLocator', () => {
]);
});
it('reads `title` property', async () => {
const sut = ConfigClusterLocator.fromConfig(
new ConfigReader({
clusters: [
{
name: 'cluster-name',
title: 'cluster-title',
url: 'url',
authMetadata: { 'kubernetes.io/auth-provider': 'serviceAccount' },
},
],
}),
authStrategy,
);
const result = await sut.getClusters();
expect(result).toEqual([
expect.objectContaining({ title: 'cluster-title' }),
]);
});
it('two clusters returns two cluster details', async () => {
const config: Config = new ConfigReader({
clusters: [
@@ -56,8 +56,10 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier {
`'authMetadata.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`,
);
}
const title = c.getOptionalString('title');
const clusterDetails: ClusterDetails = {
name,
...(title && { title }),
url: c.getString('url'),
skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false,
skipMetricsLookup: c.getOptionalBoolean('skipMetricsLookup') ?? false,
@@ -19,6 +19,7 @@ exports[`CatalogClusterLocator returns the aws cluster details provided by annot
"name": "owned",
"skipMetricsLookup": false,
"skipTLSVerify": false,
"title": undefined,
"url": "https://apiserver.com",
}
`;
@@ -42,6 +43,7 @@ exports[`CatalogClusterLocator returns the cluster details provided by annotatio
"name": "owned",
"skipMetricsLookup": true,
"skipTLSVerify": true,
"title": "title",
"url": "https://apiserver.com",
}
`;
@@ -278,6 +278,32 @@ describe('API integration tests', () => {
],
});
});
it('surfaces cluster title', async () => {
const { server } = await startTestBackend({
features: [
minimalValidConfigService,
import('@backstage/plugin-kubernetes-backend/alpha'),
withClusters([
{
name: 'cluster-name',
title: 'cluster-title',
url: 'url',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount',
},
},
]),
],
});
app = server;
const response = await request(app).get('/api/kubernetes/clusters');
expect(response.body).toEqual({
items: [expect.objectContaining({ title: 'cluster-title' })],
});
});
});
describe('post /services/:serviceId', () => {
@@ -381,6 +381,7 @@ export class KubernetesBuilder {
return {
name: cd.name,
title: cd.title,
dashboardUrl: cd.dashboardUrl,
authProvider,
...(oidcTokenProvider && { oidcTokenProvider }),
@@ -29,6 +29,7 @@ import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import {
FetchResponse,
KubernetesRequestAuth,
ObjectsByEntityResponse,
} from '@backstage/plugin-kubernetes-common';
@@ -104,7 +105,7 @@ describe('KubernetesFanOutHandler', () => {
],
};
const mockClusterResourceMap = {
const mockClusterResourceMap: Record<string, FetchResponse[]> = {
'test-cluster': [
{
resources: [
@@ -358,17 +359,16 @@ describe('KubernetesFanOutHandler', () => {
describe('getKubernetesObjectsByEntity', () => {
it('retrieve objects for one cluster', async () => {
getClustersByEntity.mockImplementation(() =>
Promise.resolve({
clusters: [
{
name: 'test-cluster',
url: '',
authMetadata: {},
},
],
}),
);
getClustersByEntity.mockResolvedValue({
clusters: [
{
name: 'test-cluster',
title: 'cluster-title',
url: '',
authMetadata: {},
},
],
});
sut = getKubernetesFanOutHandler([]);
@@ -386,11 +386,12 @@ describe('KubernetesFanOutHandler', () => {
new Set(['ns-test-component-test-cluster']),
expect.anything(),
);
expect(result).toStrictEqual({
expect(result).toStrictEqual<ObjectsByEntityResponse>({
items: [
{
cluster: {
name: 'test-cluster',
title: 'cluster-title',
},
errors: [],
podMetrics: [POD_METRICS_FIXTURE],
@@ -335,6 +335,7 @@ export class KubernetesFanOutHandler {
const objects: ClusterObjects = {
cluster: {
name: clusterDetails.name,
...(clusterDetails.title && { title: clusterDetails.title }),
},
podMetrics: toClientSafePodMetrics(metrics),
resources: result.responses,
+1
View File
@@ -112,6 +112,7 @@ export interface ClusterAttributes {
dashboardParameters?: JsonObject;
dashboardUrl?: string;
name: string;
title?: string;
}
// @public (undocumented)
+5 -1
View File
@@ -67,9 +67,13 @@ export interface KubernetesRequestBody {
/** @public */
export interface ClusterAttributes {
/**
* Specifies the name of the Kubernetes cluster.
* Name of the Kubernetes cluster; used as an internal identifier.
*/
name: string;
/**
* Human-readable name for the cluster, to be dispayed in UIs.
*/
title?: string;
/**
* Specifies the link to the Kubernetes dashboard managing this cluster.
* @remarks
+1
View File
@@ -51,6 +51,7 @@ export interface ClusterDetails {
skipMetricsLookup?: boolean;
// (undocumented)
skipTLSVerify?: boolean;
title?: string;
// (undocumented)
url: string;
}
+5 -1
View File
@@ -67,9 +67,13 @@ export type AuthMetadata = Record<string, string>;
*/
export interface ClusterDetails {
/**
* Specifies the name of the Kubernetes cluster.
* Name of the Kubernetes cluster; used as an internal identifier.
*/
name: string;
/**
* Human-readable name for the cluster, to be dispayed in UIs.
*/
title?: string;
url: string;
authMetadata: AuthMetadata;
skipTLSVerify?: boolean;
+5 -3
View File
@@ -176,11 +176,13 @@ export type ErrorPanelProps = {
// @public (undocumented)
export const ErrorReporting: ({
detectedErrors,
clusters,
}: ErrorReportingProps) => React_3.JSX.Element;
// @public (undocumented)
export type ErrorReportingProps = {
detectedErrors: DetectedErrorsByCluster;
clusters: ClusterAttributes[];
};
// @public
@@ -653,7 +655,7 @@ export interface PendingPodContentProps {
// @public
export interface PodAndErrors {
// (undocumented)
clusterName: string;
cluster: ClusterAttributes;
// (undocumented)
errors: DetectedError[];
// (undocumented)
@@ -690,7 +692,7 @@ export const PodExecTerminalDialog: (
// @public
export interface PodExecTerminalProps {
// (undocumented)
clusterName: string;
cluster: ClusterAttributes;
// (undocumented)
containerName: string;
// (undocumented)
@@ -748,7 +750,7 @@ export const PodNamesWithMetricsContext: React_2.Context<
// @public
export interface PodScope {
// (undocumented)
clusterName: string;
cluster: ClusterAttributes;
// (undocumented)
podName: string;
// (undocumented)
@@ -56,4 +56,25 @@ describe('Cluster', () => {
expect(screen.getByText('cluster-1')).toBeInTheDocument();
expect(screen.getByText('10 pods')).toBeInTheDocument();
});
it('renders title', async () => {
await renderInTestApp(
<Cluster
{...({
clusterObjects: {
cluster: {
name: 'cluster-1',
title: 'Cluster Number One',
},
resources: [],
podMetrics: [],
errors: [],
},
podsWithErrors: new Set<string>(),
} as any)}
/>,
);
expect(screen.getByText('Cluster Number One')).toBeInTheDocument();
});
});
@@ -132,7 +132,9 @@ export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => {
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<ClusterSummary
clusterName={clusterObjects.cluster.name}
clusterName={
clusterObjects.cluster.title || clusterObjects.cluster.name
}
totalNumberOfPods={groupedResponses.pods.length}
numberOfPodsWithErrors={podsWithErrors.size}
/>
@@ -26,7 +26,7 @@ describe('ErrorPanel', () => {
entityName="THIS_ENTITY"
clustersWithErrors={[
{
cluster: { name: 'THIS_CLUSTER' },
cluster: { name: 'THIS_CLUSTER', title: 'This Fine Cluster' },
resources: [],
podMetrics: [],
errors: [
@@ -50,7 +50,7 @@ describe('ErrorPanel', () => {
// message
expect(screen.getByText('Errors:')).toBeInTheDocument();
expect(screen.getByText('Cluster: THIS_CLUSTER')).toBeInTheDocument();
expect(screen.getByText('Cluster: This Fine Cluster')).toBeInTheDocument();
expect(
screen.getByText(
"Error fetching Kubernetes resource: 'some/resource', error: SYSTEM_ERROR, status code: 500",
@@ -25,7 +25,9 @@ const clustersWithErrorsToErrorMessage = (
return clustersWithErrors.map((c, i) => {
return (
<div key={i}>
<Typography variant="body2">{`Cluster: ${c.cluster.name}`}</Typography>
<Typography variant="body2">{`Cluster: ${
c.cluster.title || c.cluster.name
}`}</Typography>
{c.errors.map((e, j) => {
return (
<Typography variant="body2" key={j}>
@@ -0,0 +1,150 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { DetectedError } from '@backstage/plugin-kubernetes-common';
import { renderInTestApp } from '@backstage/test-utils';
import { MatcherFunction, screen } from '@testing-library/react';
import { ErrorReporting } from './ErrorReporting';
describe('ErrorReporting', () => {
const matchTextContent =
(text: string): MatcherFunction =>
(_, node) =>
node?.textContent?.includes(text) ?? false;
it('sorts errors by severity', async () => {
await renderInTestApp(
<ErrorReporting
detectedErrors={
new Map<string, DetectedError[]>([
[
'cluster',
[
{
type: 'readiness-probe-taking-too-long',
message:
'The container my-container failed to start properly, but is not crashing',
severity: 4,
proposedFix: undefined,
sourceRef: {
name: 'my-pod',
namespace: 'default',
kind: 'Pod',
apiGroup: 'v1',
},
occurrenceCount: 1,
},
{
type: 'condition-message-present',
message: 'some condition message',
severity: 6,
sourceRef: {
name: 'my-deployment',
namespace: 'default',
kind: 'Deployment',
apiGroup: 'apps/v1',
},
occurrenceCount: 1,
},
],
],
])
}
clusters={[{ name: 'cluster' }]}
/>,
);
expect(screen.getAllByRole('row')).toEqual([
expect.anything(),
screen.getByText(matchTextContent('some condition message'), {
selector: 'tr',
}),
screen.getByText(
matchTextContent(
'The container my-container failed to start properly, but is not crashing',
),
{ selector: 'tr' },
),
expect.anything(),
]);
});
it('displays cluster name', async () => {
await renderInTestApp(
<ErrorReporting
detectedErrors={
new Map<string, DetectedError[]>([
[
'my-cluster',
[
{
type: 'condition-message-present',
message: 'some condition message',
severity: 6,
sourceRef: {
name: 'my-deployment',
namespace: 'default',
kind: 'Deployment',
apiGroup: 'apps/v1',
},
occurrenceCount: 1,
},
],
],
])
}
clusters={[{ name: 'my-cluster' }]}
/>,
);
expect(
screen.getByRole('cell', { name: 'my-cluster' }),
).toBeInTheDocument();
});
it('displays cluster title when specified', async () => {
await renderInTestApp(
<ErrorReporting
detectedErrors={
new Map<string, DetectedError[]>([
[
'my-cluster',
[
{
type: 'condition-message-present',
message: 'some condition message',
severity: 6,
sourceRef: {
name: 'my-deployment',
namespace: 'default',
kind: 'Deployment',
apiGroup: 'apps/v1',
},
occurrenceCount: 1,
},
],
],
])
}
clusters={[{ name: 'my-cluster', title: 'cluster-title' }]}
/>,
);
expect(
screen.getByRole('cell', { name: 'cluster-title' }),
).toBeInTheDocument();
});
});
@@ -15,6 +15,7 @@
*/
import * as React from 'react';
import {
ClusterAttributes,
DetectedError,
DetectedErrorsByCluster,
} from '@backstage/plugin-kubernetes-common';
@@ -27,13 +28,14 @@ import { Table, TableColumn } from '@backstage/core-components';
*/
export type ErrorReportingProps = {
detectedErrors: DetectedErrorsByCluster;
clusters: ClusterAttributes[];
};
const columns: TableColumn<Row>[] = [
{
title: 'cluster',
width: '10%',
render: (row: Row) => row.clusterName,
render: (row: Row) => row.cluster.title || row.cluster.name,
},
{
title: 'namespace',
@@ -60,7 +62,7 @@ const columns: TableColumn<Row>[] = [
];
interface Row {
clusterName: string;
cluster: ClusterAttributes;
error: DetectedError;
}
@@ -78,11 +80,14 @@ const sortBySeverity = (a: Row, b: Row) => {
*
* @public
*/
export const ErrorReporting = ({ detectedErrors }: ErrorReportingProps) => {
export const ErrorReporting = ({
detectedErrors,
clusters,
}: ErrorReportingProps) => {
const errors = Array.from(detectedErrors.entries())
.flatMap(([clusterName, resourceErrors]) => {
return resourceErrors.map(e => ({
clusterName,
cluster: clusters.find(c => c.name === clusterName)!,
error: e,
}));
})
@@ -32,7 +32,7 @@ global.TextEncoder = require('util').TextEncoder;
const textEncoder = new TextEncoder();
describe('PodExecTerminal', () => {
const clusterName = 'cluster1';
const cluster = { name: 'cluster1' };
const containerName = 'container2';
const podName = 'pod1';
const podNamespace = 'podNamespace';
@@ -45,7 +45,7 @@ describe('PodExecTerminal', () => {
await renderInTestApp(
<TestApiProvider apis={[[discoveryApiRef, mockDiscoveryApi]]}>
<PodExecTerminal
clusterName={clusterName}
cluster={cluster}
containerName={containerName}
podName={podName}
podNamespace={podNamespace}
@@ -68,7 +68,7 @@ describe('PodExecTerminal', () => {
await renderInTestApp(
<TestApiProvider apis={[[discoveryApiRef, mockDiscoveryApi]]}>
<PodExecTerminal
clusterName={clusterName}
cluster={cluster}
containerName={containerName}
podName={podName}
podNamespace={podNamespace}
@@ -16,6 +16,7 @@
import 'xterm/css/xterm.css';
import { discoveryApiRef, useApi } from '@backstage/core-plugin-api';
import { ClusterAttributes } from '@backstage/plugin-kubernetes-common';
import { createStyles, makeStyles, Theme } from '@material-ui/core';
import React, { useEffect, useMemo, useState } from 'react';
import { Terminal } from 'xterm';
@@ -29,7 +30,7 @@ import { PodExecTerminalAttachAddon } from './PodExecTerminalAttachAddon';
* @public
*/
export interface PodExecTerminalProps {
clusterName: string;
cluster: ClusterAttributes;
containerName: string;
podName: string;
podNamespace: string;
@@ -27,7 +27,7 @@ import { PodExecTerminal, PodExecTerminalProps } from './PodExecTerminal';
* @public
*/
export const PodExecTerminalDialog = (props: PodExecTerminalProps) => {
const { clusterName, containerName, podName } = props;
const { cluster, containerName, podName } = props;
const isPodExecTerminalSupported = useIsPodExecTerminalSupported();
@@ -42,7 +42,9 @@ export const PodExecTerminalDialog = (props: PodExecTerminalProps) => {
isPodExecTerminalSupported.loading ||
!isPodExecTerminalSupported.value
}
title={`${podName} - ${containerName} terminal shell on cluster ${clusterName}`}
title={`${podName} - ${containerName} terminal shell on cluster ${
cluster.title || cluster.name
}`}
>
<PodExecTerminal {...props} />
</KubernetesDialog>
@@ -26,7 +26,7 @@ describe('ErrorList', () => {
<ErrorList
podAndErrors={[
{
clusterName: 'some-cluster',
cluster: { name: 'some-cluster' },
pod: {
metadata: {
name: 'some-pod',
@@ -82,7 +82,7 @@ export const ErrorList = ({ podAndErrors }: ErrorListProps) => {
<FixDialog
pod={onlyPodWithErrors.pod}
error={error}
clusterName={onlyPodWithErrors.clusterName}
clusterName={onlyPodWithErrors.cluster.name}
/>
</Grid>
</Grid>
@@ -119,7 +119,7 @@ export const FixDialog: React.FC<FixDialogProps> = ({
containerScope={{
podName: pod.metadata?.name ?? 'unknown',
podNamespace: pod.metadata?.namespace ?? 'unknown',
clusterName: clusterName,
cluster: { name: clusterName },
containerName: pf.container,
}}
/>
@@ -36,7 +36,7 @@ describe('ContainerCard', () => {
podScope: {
name: 'some-name',
namespace: 'some-namespace',
clusterName: 'some-cluster',
cluster: { name: 'some-cluster' },
},
containerSpec: {
readinessProbe: {},
@@ -71,7 +71,7 @@ describe('ContainerCard', () => {
podScope: {
podName: 'some-name',
podNamespace: 'some-namespace',
clusterName: 'some-cluster',
cluster: { name: 'some-cluster' },
},
containerSpec: {},
containerStatus: {
@@ -100,7 +100,7 @@ describe('ContainerCard', () => {
podScope: {
podName: 'some-name',
podNamespace: 'some-namespace',
clusterName: 'some-cluster',
cluster: { name: 'some-cluster' },
},
containerSpec: {},
containerStatus: {
@@ -233,7 +233,7 @@ export const ContainerCard: React.FC<ContainerCardProps> = ({
/>
{isPodExecTerminalEnabled && (
<PodExecTerminalDialog
clusterName={podScope.clusterName}
cluster={podScope.cluster}
containerName={containerStatus.name}
podName={podScope.podName}
podNamespace={podScope.podNamespace}
@@ -37,7 +37,7 @@ describe('PodDrawer', () => {
{...({
open: true,
podAndErrors: {
clusterName: 'some-cluster-1',
cluster: { name: 'some-cluster-1' },
pod: {
metadata: {
name: 'some-pod',
@@ -79,7 +79,7 @@ export interface PodDrawerProps {
*/
export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => {
const classes = useDrawerContentStyles();
const podMetrics = usePodMetrics(podAndErrors.clusterName, podAndErrors.pod);
const podMetrics = usePodMetrics(podAndErrors.cluster.name, podAndErrors.pod);
return (
<KubernetesDrawer
@@ -161,7 +161,7 @@ export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => {
podName: podAndErrors.pod.metadata?.name ?? 'unknown',
podNamespace:
podAndErrors.pod.metadata?.namespace ?? 'unknown',
clusterName: podAndErrors.clusterName,
cluster: podAndErrors.cluster,
}}
containerSpec={containerSpec}
containerStatus={containerStatus}
@@ -42,7 +42,11 @@ export const PodLogsDialog = ({ containerScope }: PodLogsDialogProps) => {
buttonIcon={<SubjectIcon />}
buttonText="Logs"
disabled={false}
title={`${containerScope.podName} - ${containerScope.containerName} logs on cluster ${containerScope.clusterName}`}
title={`${containerScope.podName} - ${
containerScope.containerName
} logs on cluster ${
containerScope.cluster.title || containerScope.cluster.name
}`}
>
<PodLogs containerScope={containerScope} />
</KubernetesDialog>
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ClusterAttributes } from '@backstage/plugin-kubernetes-common';
/**
* Contains the details needed to make a log request to Kubernetes, except the container name
@@ -22,7 +23,7 @@
export interface PodScope {
podName: string;
podNamespace: string;
clusterName: string;
cluster: ClusterAttributes;
}
/**
@@ -41,7 +41,7 @@ export const usePodLogs = ({ containerScope, previous }: PodLogsOptions) => {
podName: containerScope.podName,
namespace: containerScope.podNamespace,
containerName: containerScope.containerName,
clusterName: containerScope.clusterName,
clusterName: containerScope.cluster.name,
previous,
});
}, [JSON.stringify(containerScope)]);
+1 -2
View File
@@ -80,7 +80,6 @@ const READY: TableColumn<Pod>[] = [
];
const PodDrawerTrigger = ({ pod }: { pod: Pod }) => {
const cluster = useContext(ClusterContext);
const errors = useMatchingErrors({
kind: 'Pod',
apiVersion: 'v1',
@@ -90,7 +89,7 @@ const PodDrawerTrigger = ({ pod }: { pod: Pod }) => {
<PodDrawer
podAndErrors={{
pod: pod as any,
clusterName: cluster.name,
cluster: useContext(ClusterContext),
errors: errors,
}}
/>
+6 -3
View File
@@ -14,15 +14,18 @@
* limitations under the License.
*/
import { Pod } from 'kubernetes-models/v1';
import { DetectedError } from '@backstage/plugin-kubernetes-common';
import {
ClusterAttributes,
DetectedError,
} from '@backstage/plugin-kubernetes-common';
/**
* Wraps a pod with the associated detected errors and cluster name
* Wraps a pod with the associated detected errors and cluster
*
* @public
*/
export interface PodAndErrors {
clusterName: string;
cluster: ClusterAttributes;
pod: Pod;
errors: DetectedError[];
}
+6 -1
View File
@@ -50,6 +50,8 @@ export const KubernetesContent = ({
refreshIntervalMs,
);
const clusters = kubernetesObjects?.items.map(item => item.cluster) ?? [];
const clustersWithErrors =
kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? [];
@@ -93,7 +95,10 @@ export const KubernetesContent = ({
{kubernetesObjects && (
<Grid container spacing={3} direction="column">
<Grid item>
<ErrorReporting detectedErrors={detectedErrors} />
<ErrorReporting
detectedErrors={detectedErrors}
clusters={clusters}
/>
</Grid>
<Grid item>
<Typography variant="h3">Your Clusters</Typography>