Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upcoming: [M3-8019] – Add Encrypted/Not Encrypted status to Linode Detail summary header #10537

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Changed
---

Add lke_cluster_id to Linode interface ([#10537](https://github.com/linode/manager/pull/10537))
1 change: 1 addition & 0 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Linode {
ipv4: string[];
ipv6: string | null;
label: string;
lke_cluster_id: number | null;
placement_group?: PlacementGroupPayload; // If not in a placement group, this will be excluded from the response.
type: string | null;
status: LinodeStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add Encrypted / Not Encrypted status to Linode Detail header ([#10537](https://github.com/linode/manager/pull/10537))
4 changes: 4 additions & 0 deletions packages/manager/src/__data__/linodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const linode1: Linode = {
ipv4: ['97.107.143.78', '98.107.143.78', '99.107.143.78'],
ipv6: '2600:3c03::f03c:91ff:fe0a:109a/64',
label: 'test',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down Expand Up @@ -69,6 +70,7 @@ export const linode2: Linode = {
ipv4: ['97.107.143.49'],
ipv6: '2600:3c03::f03c:91ff:fe0a:0d7a/64',
label: 'another-test',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down Expand Up @@ -114,6 +116,7 @@ export const linode3: Linode = {
ipv4: ['97.107.143.49'],
ipv6: '2600:3c03::f03c:91ff:fe0a:0d7a/64',
label: 'another-test',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down Expand Up @@ -159,6 +162,7 @@ export const linode4: Linode = {
ipv4: ['97.107.143.49'],
ipv6: '2600:3c03::f03c:91ff:fe0a:0d7a/64',
label: 'another-test-eu',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/assets/icons/lock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/manager/src/assets/icons/unlock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ export const DISK_ENCRYPTION_BACKUPS_CAVEAT_COPY =
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.';

export const UNENCRYPTED_STANDARD_LINODE_GUIDANCE_COPY =
'Rebuild this Linode to enable or disable disk encryption.';

export const DISK_ENCRYPTION_IMAGES_CAVEAT_COPY =
'Virtual Machine Images are not encrypted.';
1 change: 1 addition & 0 deletions packages/manager/src/factories/linodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export const linodeFactory = Factory.Sync.makeFactory<Linode>({
ipv4: ['50.116.6.212', '192.168.203.1'],
ipv6: '2600:3c00::f03c:92ff:fee2:6c40/64',
label: Factory.each((i) => `linode-${i}`),
lke_cluster_id: null,
placement_group: placementGroupFactory.build({
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,10 @@ export const EncryptedStatus = ({
</>
) : encryptionStatus === 'disabled' ? (
<>
<Unlock />
<StyledTypography>Not Encrypted</StyledTypography>
<Unlock style={{ minWidth: 16 }} />
<StyledTypography sx={{ whiteSpace: 'nowrap' }}>
Not Encrypted
</StyledTypography>
{tooltipText ? <TooltipIcon status="help" text={tooltipText} /> : null}
</>
) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { HttpResponse, http, server } from 'src/mocks/testServer';
import { queryClientFactory } from 'src/queries/base';
import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers';

import { encryptionStatusTestId } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable';
import { LinodeEntityDetail } from './LinodeEntityDetail';
import { getSubnetsString } from './LinodeEntityDetailBody';
import { LinodeHandlers } from './LinodesLanding/LinodesLanding';
Expand All @@ -34,6 +35,29 @@ describe('Linode Entity Detail', () => {
const vpcSectionTestId = 'vpc-section-title';
const assignedVPCLabelTestId = 'assigned-vpc-label';

const mocks = vi.hoisted(() => {
return {
useIsDiskEncryptionFeatureEnabled: vi.fn(),
};
});

vi.mock('src/components/DiskEncryption/utils.ts', async () => {
const actual = await vi.importActual<any>(
'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('should not display the VPC section if the linode is not assigned to a VPC', async () => {
const account = accountFactory.build({
capabilities: [...accountCapabilitiesWithoutVPC, 'VPCs'],
Expand Down Expand Up @@ -104,6 +128,41 @@ describe('Linode Entity Detail', () => {
expect(getByTestId(assignedVPCLabelTestId).innerHTML).toEqual('test-vpc');
});
});

it('should not display the encryption status of the linode if the account lacks the capability or the feature flag is off', () => {
// situation where isDiskEncryptionFeatureEnabled === false
const { queryByTestId } = renderWithTheme(
<LinodeEntityDetail
handlers={handlers}
id={10}
linode={linode}
openTagDrawer={vi.fn()}
/>
);
const encryptionStatusFragment = queryByTestId(encryptionStatusTestId);

expect(encryptionStatusFragment).not.toBeInTheDocument();
});

it('should display the encryption status of the linode when Disk Encryption is enabled and the user has the account capability', () => {
mocks.useIsDiskEncryptionFeatureEnabled.mockImplementationOnce(() => {
return {
isDiskEncryptionFeatureEnabled: true,
};
});

const { queryByTestId } = renderWithTheme(
<LinodeEntityDetail
handlers={handlers}
id={10}
linode={linode}
openTagDrawer={vi.fn()}
/>
);
const encryptionStatusFragment = queryByTestId(encryptionStatusTestId);

expect(encryptionStatusFragment).toBeInTheDocument();
});
});

describe('getSubnetsString function', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ export const LinodeEntityDetail = (props: Props) => {
body={
<LinodeEntityDetailBody
configInterfaceWithVPC={configInterfaceWithVPC}
encryptionStatus={linode.disk_encryption}
gbRAM={linode.specs.memory / 1024}
gbStorage={linode.specs.disk / 1024}
ipv4={linode.ipv4}
ipv6={trimmedIPv6}
isLKELinode={Boolean(linode.lke_cluster_id)}
isVPCOnlyLinode={isVPCOnlyLinode}
linodeId={linode.id}
linodeIsInDistributedRegion={linodeIsInDistributedRegion}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
import { HashLink } from 'react-router-hash-link';

import { Box } from 'src/components/Box';
import {
DISK_ENCRYPTION_NODE_POOL_GUIDANCE_COPY as UNENCRYPTED_LKE_LINODE_GUIDANCE_COPY,
UNENCRYPTED_STANDARD_LINODE_GUIDANCE_COPY,
} from 'src/components/DiskEncryption/constants';
import { useIsDiskEncryptionFeatureEnabled } from 'src/components/DiskEncryption/utils';
import { Link } from 'src/components/Link';
import { Typography, TypographyProps } from 'src/components/Typography';
import { AccessTable } from 'src/features/Linodes/AccessTable';
import { useProfile } from 'src/queries/profile';
import { pluralize } from 'src/utilities/pluralize';

import { encryptionStatusTestId } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable';
import { EncryptedStatus } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable';
import {
StyledBodyGrid,
StyledColumnLabelGrid,
Expand All @@ -24,7 +32,11 @@ import { ipv4TableID } from './LinodesDetail/LinodeNetworking/LinodeIPAddresses'
import { lishLink, sshLink } from './LinodesDetail/utilities';
import { LinodeHandlers } from './LinodesLanding/LinodesLanding';

import type { Interface, Linode } from '@linode/api-v4/lib/linodes/types';
import type {
EncryptionStatus,
Interface,
Linode,
} from '@linode/api-v4/lib/linodes/types';
import type { Subnet } from '@linode/api-v4/lib/vpcs';

interface LinodeEntityDetailProps {
Expand All @@ -41,10 +53,12 @@ export interface Props extends LinodeEntityDetailProps {

export interface BodyProps {
configInterfaceWithVPC?: Interface;
encryptionStatus: EncryptionStatus | undefined;
gbRAM: number;
gbStorage: number;
ipv4: Linode['ipv4'];
ipv6: Linode['ipv6'];
isLKELinode: boolean; // indicates whether linode belongs to an LKE cluster
isVPCOnlyLinode: boolean;
linodeId: number;
linodeIsInDistributedRegion: boolean;
Expand All @@ -58,10 +72,12 @@ export interface BodyProps {
export const LinodeEntityDetailBody = React.memo((props: BodyProps) => {
const {
configInterfaceWithVPC,
encryptionStatus,
gbRAM,
gbStorage,
ipv4,
ipv6,
isLKELinode,
isVPCOnlyLinode,
linodeId,
linodeIsInDistributedRegion,
Expand All @@ -77,6 +93,14 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => {

const theme = useTheme();

const {
isDiskEncryptionFeatureEnabled,
} = useIsDiskEncryptionFeatureEnabled();

// @ TODO LDE: Remove usages of this variable once LDE is fully rolled out (being used to determine formatting adjustments currently)
const isDisplayingEncryptedStatus =
isDiskEncryptionFeatureEnabled && Boolean(encryptionStatus);

// Filter and retrieve subnets associated with a specific Linode ID
const linodeAssociatedSubnets = vpcLinodeIsAssignedTo?.subnets.filter(
(subnet) => subnet.linodes.some((linode) => linode.id === linodeId)
Expand All @@ -97,11 +121,14 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => {
<Grid
container
flexDirection={matchesLgUp ? 'row' : 'column'}
sm={3}
sm={isDisplayingEncryptedStatus ? 4 : 3}
spacing={0}
xs={12}
>
<StyledColumnLabelGrid mb={matchesLgUp ? 0 : 2} xs={12}>
<StyledColumnLabelGrid
mb={matchesLgUp && !isDisplayingEncryptedStatus ? 0 : 2}
xs={12}
>
Summary
</StyledColumnLabelGrid>
<StyledSummaryGrid container spacing={1}>
Expand All @@ -121,9 +148,28 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => {
{pluralize('Volume', 'Volumes', numVolumes)}
</Typography>
</Grid>
{isDiskEncryptionFeatureEnabled && encryptionStatus && (
<Grid>
<Box
alignItems="center"
data-testid={encryptionStatusTestId}
display="flex"
flexDirection="row"
>
<EncryptedStatus
tooltipText={
isLKELinode
? UNENCRYPTED_LKE_LINODE_GUIDANCE_COPY
: UNENCRYPTED_STANDARD_LINODE_GUIDANCE_COPY
}
encryptionStatus={encryptionStatus}
/>
</Box>
</Grid>
)}
</StyledSummaryGrid>
</Grid>
<Grid container sm={9} xs={12}>
<Grid container sm={isDisplayingEncryptedStatus ? 8 : 9} xs={12}>
<Grid container xs={12}>
<AccessTable
footer={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const props: AddonsPanelProps = {
ipv4: ['45.56.75.98'],
ipv6: '2600:3c00::f03c:93ff:fe85:576d/128',
label: 'test_instance',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down Expand Up @@ -107,6 +108,7 @@ const props: AddonsPanelProps = {
ipv4: ['192.168.139.183', '139.144.17.202'],
ipv6: '2600:3c04::f03c:93ff:fe75:0612/128',
label: 'debian-ca-central',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down Expand Up @@ -151,6 +153,7 @@ const props: AddonsPanelProps = {
ipv4: ['45.79.74.95'],
ipv6: '2600:3c01::f03c:93ff:fe75:e4f9/128',
label: 'almalinux-us-west',
lke_cluster_id: null,
placement_group: {
affinity_type: 'anti_affinity:local',
id: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('LinodeRow', () => {
ipv6={linode.ipv6 || ''}
key={`linode-row-${1}`}
label={linode.label}
lke_cluster_id={linode.lke_cluster_id}
placement_group={linode.placement_group}
region={linode.region}
specs={linode.specs}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export const ListView = (props: RenderLinodesProps) => {
ipv6={linode.ipv6 || ''}
key={`linode-row-${idx}`}
label={linode.label}
lke_cluster_id={linode.lke_cluster_id}
maintenance={linode.maintenance}
placement_group={linode.placement_group}
region={linode.region}
specs={linode.specs}
Expand All @@ -54,7 +56,6 @@ export const ListView = (props: RenderLinodesProps) => {
type={linode.type}
updated={linode.updated}
watchdog_enabled={linode.watchdog_enabled}
maintenance={linode.maintenance}
/>
))}
</>
Expand Down
Loading