From 1c5e92617d7f77a78454284a7dcb9ec160f05a1d Mon Sep 17 00:00:00 2001
From: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>
Date: Thu, 23 May 2024 10:03:22 -0400
Subject: [PATCH] =?UTF-8?q?upcoming:=20[M3-8032]=20=E2=80=93=20Add=20Encry?=
=?UTF-8?q?pted/Not=20Encrypted=20status=20to=20Node=20Pool=20table=20(#10?=
=?UTF-8?q?480)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/api-v4/src/images/images.ts | 4 +-
packages/api-v4/src/kubernetes/types.ts | 2 +-
...r-10480-upcoming-features-1716321944627.md | 5 ++
.../src/assets/icons/divider-vertical.svg | 3 +
packages/manager/src/assets/icons/lock.svg | 3 +
packages/manager/src/assets/icons/unlock.svg | 4 ++
.../components/DiskEncryption/constants.tsx | 3 +
.../NodePoolsDisplay/NodePool.tsx | 17 +++--
.../NodePoolsDisplay/NodePoolsDisplay.tsx | 11 +--
.../NodePoolsDisplay/NodeTable.styles.ts | 15 +++-
.../NodePoolsDisplay/NodeTable.test.tsx | 50 ++++++++++++-
.../NodePoolsDisplay/NodeTable.tsx | 71 +++++++++++++++++--
packages/manager/src/mocks/serverHandlers.ts | 9 ++-
13 files changed, 174 insertions(+), 23 deletions(-)
create mode 100644 packages/manager/.changeset/pr-10480-upcoming-features-1716321944627.md
create mode 100644 packages/manager/src/assets/icons/divider-vertical.svg
create mode 100644 packages/manager/src/assets/icons/lock.svg
create mode 100644 packages/manager/src/assets/icons/unlock.svg
diff --git a/packages/api-v4/src/images/images.ts b/packages/api-v4/src/images/images.ts
index 9c9984bdbd1..720d75bcdda 100644
--- a/packages/api-v4/src/images/images.ts
+++ b/packages/api-v4/src/images/images.ts
@@ -11,8 +11,8 @@ import Request, {
setURL,
setXFilter,
} from '../request';
-import { Filter, Params, ResourcePage as Page } from '../types';
-import {
+import type { Filter, Params, ResourcePage as Page } from '../types';
+import type {
CreateImagePayload,
Image,
ImageUploadPayload,
diff --git a/packages/api-v4/src/kubernetes/types.ts b/packages/api-v4/src/kubernetes/types.ts
index c8d25118e35..8e2d176572c 100644
--- a/packages/api-v4/src/kubernetes/types.ts
+++ b/packages/api-v4/src/kubernetes/types.ts
@@ -1,4 +1,4 @@
-import type { EncryptionStatus } from 'src/linodes';
+import type { EncryptionStatus } from '../linodes';
export interface KubernetesCluster {
created: string;
diff --git a/packages/manager/.changeset/pr-10480-upcoming-features-1716321944627.md b/packages/manager/.changeset/pr-10480-upcoming-features-1716321944627.md
new file mode 100644
index 00000000000..ccf6bb170dd
--- /dev/null
+++ b/packages/manager/.changeset/pr-10480-upcoming-features-1716321944627.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Upcoming Features
+---
+
+Add Encrypted/Not Encrypted status to LKE Node Pool table ([#10480](https://github.com/linode/manager/pull/10480))
diff --git a/packages/manager/src/assets/icons/divider-vertical.svg b/packages/manager/src/assets/icons/divider-vertical.svg
new file mode 100644
index 00000000000..79add159022
--- /dev/null
+++ b/packages/manager/src/assets/icons/divider-vertical.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/manager/src/assets/icons/lock.svg b/packages/manager/src/assets/icons/lock.svg
new file mode 100644
index 00000000000..ca135909b4f
--- /dev/null
+++ b/packages/manager/src/assets/icons/lock.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/manager/src/assets/icons/unlock.svg b/packages/manager/src/assets/icons/unlock.svg
new file mode 100644
index 00000000000..ce413046282
--- /dev/null
+++ b/packages/manager/src/assets/icons/unlock.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/manager/src/components/DiskEncryption/constants.tsx b/packages/manager/src/components/DiskEncryption/constants.tsx
index dbdce42a9c7..5d0ffe10ec8 100644
--- a/packages/manager/src/components/DiskEncryption/constants.tsx
+++ b/packages/manager/src/components/DiskEncryption/constants.tsx
@@ -19,3 +19,6 @@ export const DISK_ENCRYPTION_UNAVAILABLE_IN_REGION_COPY =
export const DISK_ENCRYPTION_BACKUPS_CAVEAT_COPY =
'Virtual Machine Backups are not encrypted.';
+
+export const DISK_ENCRYPTION_NODE_POOL_GUIDANCE_COPY =
+ 'To enable disk encryption, delete the node pool and create a new node pool. New node pools are always encrypted.';
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePool.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePool.tsx
index ed4ff6d7878..02e61f7aec2 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePool.tsx
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePool.tsx
@@ -1,7 +1,3 @@
-import {
- AutoscaleSettings,
- PoolNodeResponse,
-} from '@linode/api-v4/lib/kubernetes';
import { Theme } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
@@ -13,8 +9,15 @@ import { Typography } from 'src/components/Typography';
import { NodeTable } from './NodeTable';
+import type {
+ AutoscaleSettings,
+ PoolNodeResponse,
+} from '@linode/api-v4/lib/kubernetes';
+import type { EncryptionStatus } from '@linode/api-v4/lib/linodes/types';
+
interface Props {
autoscaler: AutoscaleSettings;
+ encryptionStatus: EncryptionStatus | undefined;
handleClickResize: (poolId: number) => void;
isOnlyNodePool: boolean;
nodes: PoolNodeResponse[];
@@ -40,9 +43,10 @@ const useStyles = makeStyles()((theme: Theme) => ({
},
}));
-const NodePool: React.FC = (props) => {
+export const NodePool = (props: Props) => {
const {
autoscaler,
+ encryptionStatus,
handleClickResize,
isOnlyNodePool,
nodes,
@@ -126,6 +130,7 @@ const NodePool: React.FC = (props) => {
xs={12}
>
= (props) => {
);
};
-
-export default NodePool;
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePoolsDisplay.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePoolsDisplay.tsx
index e4f5d34f542..97cb7c652e8 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePoolsDisplay.tsx
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodePoolsDisplay.tsx
@@ -1,14 +1,14 @@
-import Grid from '@mui/material/Unstable_Grid2';
import { Theme } from '@mui/material/styles';
-import { makeStyles } from 'tss-react/mui';
+import Grid from '@mui/material/Unstable_Grid2';
import React, { useState } from 'react';
import { Waypoint } from 'react-waypoint';
+import { makeStyles } from 'tss-react/mui';
import { Button } from 'src/components/Button/Button';
import { CircleProgress } from 'src/components/CircleProgress';
import { ErrorState } from 'src/components/ErrorState/ErrorState';
-import { Typography } from 'src/components/Typography';
import { Paper } from 'src/components/Paper';
+import { Typography } from 'src/components/Typography';
import { useAllKubernetesNodePoolQuery } from 'src/queries/kubernetes';
import { useSpecificTypes } from 'src/queries/types';
import { extendTypesQueryResult } from 'src/utilities/extendType';
@@ -18,7 +18,7 @@ import { RecycleNodePoolDialog } from '../RecycleNodePoolDialog';
import { AddNodePoolDrawer } from './AddNodePoolDrawer';
import { AutoscalePoolDialog } from './AutoscalePoolDialog';
import { DeleteNodePoolDialog } from './DeleteNodePoolDialog';
-import NodePool from './NodePool';
+import { NodePool } from './NodePool';
import { RecycleNodeDialog } from './RecycleNodeDialog';
import { ResizeNodePoolDrawer } from './ResizeNodePoolDrawer';
@@ -152,7 +152,7 @@ export const NodePoolsDisplay = (props: Props) => {
{_pools?.map((thisPool) => {
- const { id, nodes } = thisPool;
+ const { disk_encryption, id, nodes } = thisPool;
const thisPoolType = types?.find(
(thisType) => thisType.id === thisPool.type
@@ -181,6 +181,7 @@ export const NodePoolsDisplay = (props: Props) => {
setIsRecycleNodeOpen(true);
}}
autoscaler={thisPool.autoscaler}
+ encryptionStatus={disk_encryption}
handleClickResize={handleOpenResizeDrawer}
isOnlyNodePool={pools?.length === 1}
nodes={nodes ?? []}
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.styles.ts b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.styles.ts
index 482ab5b66fc..f272e64c72c 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.styles.ts
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.styles.ts
@@ -1,8 +1,10 @@
import { styled } from '@mui/material/styles';
+import VerticalDivider from 'src/assets/icons/divider-vertical.svg';
import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip';
import { Table } from 'src/components/Table';
import { TableRow } from 'src/components/TableRow';
+import { Typography } from 'src/components/Typography';
export const StyledTableRow = styled(TableRow, {
label: 'TableRow',
@@ -19,7 +21,6 @@ export const StyledTableRow = styled(TableRow, {
opacity: 1,
},
marginLeft: 4,
- top: 1,
}));
export const StyledTable = styled(Table, {
@@ -40,3 +41,15 @@ export const StyledCopyTooltip = styled(CopyTooltip, {
marginLeft: 4,
top: 1,
}));
+
+export const StyledVerticalDivider = styled(VerticalDivider, {
+ label: 'StyledVerticalDivider',
+})(({ theme }) => ({
+ margin: `0 ${theme.spacing(2)}`,
+}));
+
+export const StyledTypography = styled(Typography, {
+ label: 'StyledTypography',
+})(({ theme }) => ({
+ margin: `0 0 0 ${theme.spacing()}`,
+}));
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.test.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.test.tsx
index 4298d833767..49216784a3f 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.test.tsx
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.test.tsx
@@ -4,13 +4,14 @@ import { kubeLinodeFactory } from 'src/factories/kubernetesCluster';
import { linodeFactory } from 'src/factories/linodes';
import { renderWithTheme } from 'src/utilities/testHelpers';
-import { NodeTable, Props } from './NodeTable';
+import { NodeTable, Props, encryptionStatusTestId } from './NodeTable';
const mockLinodes = linodeFactory.buildList(3);
const mockKubeNodes = kubeLinodeFactory.buildList(3);
const props: Props = {
+ encryptionStatus: 'enabled',
nodes: mockKubeNodes,
openRecycleNodeDialog: vi.fn(),
poolId: 1,
@@ -20,6 +21,29 @@ const props: Props = {
beforeAll(() => linodeFactory.resetSequenceNumber());
describe('NodeTable', () => {
+ const mocks = vi.hoisted(() => {
+ return {
+ useIsDiskEncryptionFeatureEnabled: vi.fn(),
+ };
+ });
+
+ vi.mock('src/components/DiskEncryption/utils.ts', async () => {
+ const actual = await vi.importActual(
+ 'src/components/DiskEncryption/utils.ts'
+ );
+ return {
+ ...actual,
+ __esModule: true,
+ useIsDiskEncryptionFeatureEnabled: mocks.useIsDiskEncryptionFeatureEnabled.mockImplementation(
+ () => {
+ return {
+ isDiskEncryptionFeatureEnabled: false, // indicates the feature flag is off or account capability is absent
+ };
+ }
+ ),
+ };
+ });
+
it('includes label, status, and IP columns', () => {
const { findByText } = renderWithTheme();
mockLinodes.forEach(async (thisLinode) => {
@@ -28,8 +52,32 @@ describe('NodeTable', () => {
await findByText('Ready');
});
});
+
it('includes the Pool ID', () => {
const { getByText } = renderWithTheme();
getByText('Pool ID 1');
});
+
+ it('does not display the encryption status of the pool if the account lacks the capability or the feature flag is off', () => {
+ // situation where isDiskEncryptionFeatureEnabled === false
+ const { queryByTestId } = renderWithTheme();
+ const encryptionStatusFragment = queryByTestId(encryptionStatusTestId);
+
+ expect(encryptionStatusFragment).not.toBeInTheDocument();
+ });
+
+ it('displays the encryption status of the pool if the feature flag is on and the account has the capability', () => {
+ mocks.useIsDiskEncryptionFeatureEnabled.mockImplementationOnce(() => {
+ return {
+ isDiskEncryptionFeatureEnabled: true,
+ };
+ });
+
+ const { queryByTestId } = renderWithTheme();
+ const encryptionStatusFragment = queryByTestId(encryptionStatusTestId);
+
+ expect(encryptionStatusFragment).toBeInTheDocument();
+
+ mocks.useIsDiskEncryptionFeatureEnabled.mockRestore();
+ });
});
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.tsx
index abb227b83c6..be95c8ab1df 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.tsx
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.tsx
@@ -1,6 +1,10 @@
-import { PoolNodeResponse } from '@linode/api-v4/lib/kubernetes';
import * as React from 'react';
+import Lock from 'src/assets/icons/lock.svg';
+import Unlock from 'src/assets/icons/unlock.svg';
+import { Box } from 'src/components/Box';
+import { DISK_ENCRYPTION_NODE_POOL_GUIDANCE_COPY } from 'src/components/DiskEncryption/constants';
+import { useIsDiskEncryptionFeatureEnabled } from 'src/components/DiskEncryption/utils';
import OrderBy from 'src/components/OrderBy';
import Paginate from 'src/components/Paginate';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
@@ -11,26 +15,45 @@ import { TableFooter } from 'src/components/TableFooter';
import { TableHead } from 'src/components/TableHead';
import { TableRow } from 'src/components/TableRow';
import { TableSortCell } from 'src/components/TableSortCell';
+import { TooltipIcon } from 'src/components/TooltipIcon';
import { Typography } from 'src/components/Typography';
import { useAllLinodesQuery } from 'src/queries/linodes/linodes';
import { LinodeWithMaintenance } from 'src/utilities/linodes';
import { NodeRow as _NodeRow } from './NodeRow';
-import { StyledTable } from './NodeTable.styles';
+import {
+ StyledTable,
+ StyledTypography,
+ StyledVerticalDivider,
+} from './NodeTable.styles';
import type { NodeRow } from './NodeRow';
+import type { PoolNodeResponse } from '@linode/api-v4/lib/kubernetes';
+import type { EncryptionStatus } from '@linode/api-v4/lib/linodes/types';
export interface Props {
+ encryptionStatus: EncryptionStatus | undefined;
nodes: PoolNodeResponse[];
openRecycleNodeDialog: (nodeID: string, linodeLabel: string) => void;
poolId: number;
typeLabel: string;
}
+export const encryptionStatusTestId = 'encryption-status-fragment';
+
export const NodeTable = React.memo((props: Props) => {
- const { nodes, openRecycleNodeDialog, poolId, typeLabel } = props;
+ const {
+ encryptionStatus,
+ nodes,
+ openRecycleNodeDialog,
+ poolId,
+ typeLabel,
+ } = props;
const { data: linodes, error, isLoading } = useAllLinodesQuery();
+ const {
+ isDiskEncryptionFeatureEnabled,
+ } = useIsDiskEncryptionFeatureEnabled();
const rowData = nodes.map((thisNode) => nodeToRow(thisNode, linodes ?? []));
@@ -116,7 +139,26 @@ export const NodeTable = React.memo((props: Props) => {
- Pool ID {poolId}
+ {isDiskEncryptionFeatureEnabled &&
+ encryptionStatus !== undefined ? (
+
+ Pool ID {poolId}
+
+
+
+ ) : (
+ Pool ID {poolId}
+ )}
@@ -157,3 +199,24 @@ export const nodeToRow = (
nodeStatus: node.status,
};
};
+
+export const EncryptedStatus = ({
+ encryptionStatus,
+ tooltipText,
+}: {
+ encryptionStatus: EncryptionStatus;
+ tooltipText: string | undefined;
+}) => {
+ return encryptionStatus === 'enabled' ? (
+ <>
+
+ Encrypted
+ >
+ ) : encryptionStatus === 'disabled' ? (
+ <>
+
+ Not Encrypted
+ {tooltipText ? : null}
+ >
+ ) : null;
+};
diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts
index c0dceb9e76e..e1c89236f27 100644
--- a/packages/manager/src/mocks/serverHandlers.ts
+++ b/packages/manager/src/mocks/serverHandlers.ts
@@ -849,9 +849,14 @@ export const handlers = [
return HttpResponse.json(cluster);
}),
http.get('*/lke/clusters/:clusterId/pools', async () => {
- const pools = nodePoolFactory.buildList(10);
+ const encryptedPools = nodePoolFactory.buildList(5);
+ const unencryptedPools = nodePoolFactory.buildList(5, {
+ disk_encryption: 'disabled',
+ });
nodePoolFactory.resetSequenceNumber();
- return HttpResponse.json(makeResourcePage(pools));
+ return HttpResponse.json(
+ makeResourcePage([...encryptedPools, ...unencryptedPools])
+ );
}),
http.get('*/lke/clusters/*/api-endpoints', async () => {
const endpoints = kubeEndpointFactory.buildList(2);