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

feat: [M3 7015] - Create nodebalancer tab in firewalls landing #9590

Merged
merged 11 commits into from
Aug 28, 2023
2 changes: 1 addition & 1 deletion packages/api-v4/src/firewalls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type FirewallStatus = 'enabled' | 'disabled' | 'deleted';

export type FirewallRuleProtocol = 'ALL' | 'TCP' | 'UDP' | 'ICMP' | 'IPENCAP';

export type FirewallDeviceEntityType = 'linode';
export type FirewallDeviceEntityType = 'linode' | 'nodebalancer';

export type FirewallPolicyType = 'ACCEPT' | 'DROP';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Create NodeBalancer tab in Firewalls landing ([#9590](https://github.com/linode/manager/pull/9590))
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as React from 'react';

import { firewallDeviceFactory } from 'src/factories';
import { rest, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import {
FirewallDeviceLanding,
FirewallDeviceLandingProps,
} from './FirewallDeviceLanding';

import type { FirewallDeviceEntityType } from '@linode/api-v4';

const baseProps = (
type: FirewallDeviceEntityType
): FirewallDeviceLandingProps => ({
disabled: true,
firewallID: 1,
firewallLabel: 'test',
type,
});

carrillo-erik marked this conversation as resolved.
Show resolved Hide resolved
const devices = ['linode', 'nodebalancer'];

devices.forEach((device: FirewallDeviceEntityType) => {
describe(`Firewall ${device} device`, () => {
let addButton: HTMLElement;
let permissionNotice: HTMLElement;
let table: HTMLElement;

beforeEach(() => {
server.use(
rest.get('*/firewalls/*', (req, res, ctx) => {
return res(ctx.json(firewallDeviceFactory.buildList(1)));
})
);
const { getByRole, getByTestId } = renderWithTheme(
<FirewallDeviceLanding {...baseProps(device)} />
);
addButton = getByTestId('add-device-button');
permissionNotice = getByRole('alert');
table = getByRole('table');
});

it(`should render an add ${device} button`, () => {
expect(addButton).toBeInTheDocument();
});

it(`should render a disabled add ${device} button`, () => {
expect(addButton).toBeDisabled();
});

it(`should render a permission denied notice`, () => {
expect(permissionNotice).toBeInTheDocument();
});

it(`should render a table`, () => {
expect(table).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import Grid from '@mui/material/Unstable_Grid2';
import { styled } from '@mui/material/styles';
import * as React from 'react';

import { Button } from 'src/components/Button/Button';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
import { useAllFirewallDevicesQuery } from 'src/queries/firewalls';

import { AddDeviceDrawer } from './AddDeviceDrawer';
import { FirewallDevicesTable } from './FirewallDevicesTable';
import { RemoveDeviceDialog } from './RemoveDeviceDialog';

import type { FirewallDeviceEntityType } from '@linode/api-v4';

export interface FirewallDeviceLandingProps {
disabled: boolean;
firewallID: number;
firewallLabel: string;
type: FirewallDeviceEntityType;
}

const formattedTypes = {
linode: 'Linode',
nodebalancer: 'NodeBalancer',
};

export const FirewallDeviceLanding = React.memo(
(props: FirewallDeviceLandingProps) => {
const { disabled, firewallID, firewallLabel, type } = props;

const { data: allDevices, error, isLoading } = useAllFirewallDevicesQuery(
firewallID
);

const devices =
allDevices?.filter((device) => device.entity.type === type) || [];

const [
isRemoveDeviceDialogOpen,
setIsRemoveDeviceDialogOpen,
] = React.useState<boolean>(false);

const [selectedDeviceId, setSelectedDeviceId] = React.useState<number>(-1);

const selectedDevice = devices?.find(
(device) => device.id === selectedDeviceId
);

const [addDeviceDrawerOpen, setDeviceDrawerOpen] = React.useState<boolean>(
false
);

const handleClose = () => {
setDeviceDrawerOpen(false);
};

const formattedType = formattedTypes[type];

return (
<>
{disabled ? (
<Notice
text={
"You don't have permissions to modify this Firewall. Please contact an account administrator for details."
}
important
variant="error"
/>
) : null}
<Grid container direction="column">
<Grid style={{ paddingBottom: 0 }}>
<StyledTypography>
The following {formattedType}s have been assigned to this
Firewall. A {formattedType} can only be assigned to a single
Firewall.
</StyledTypography>
</Grid>
<StyledGrid>
<Button
buttonType="primary"
data-testid="add-device-button"
disabled={disabled}
onClick={() => setDeviceDrawerOpen(true)}
>
Add {formattedType}s to Firewall
</Button>
</StyledGrid>
</Grid>
<FirewallDevicesTable
triggerRemoveDevice={(id) => {
setSelectedDeviceId(id);
setIsRemoveDeviceDialogOpen(true);
}}
devices={devices ?? []}
disabled={disabled}
error={error ?? undefined}
loading={isLoading}
/>
<AddDeviceDrawer onClose={handleClose} open={addDeviceDrawerOpen} />
<RemoveDeviceDialog
device={selectedDevice}
firewallId={firewallID}
firewallLabel={firewallLabel}
linodeId={selectedDevice?.entity.id}
onClose={() => setIsRemoveDeviceDialogOpen(false)}
open={isRemoveDeviceDialogOpen}
/>
</>
);
}
);

const StyledTypography = styled(Typography, { label: 'StyledTypography' })(
({ theme }) => ({
fontSize: '0.875rem',
marginTop: theme.spacing(),
[theme.breakpoints.down('lg')]: {
marginLeft: theme.spacing(),
marginRight: theme.spacing(),
},
})
);

const StyledGrid = styled(Grid, { label: 'StyledGrid' })(({ theme }) => ({
'&.MuiGrid-item': {
paddingTop: 0,
},
display: 'flex',
justifyContent: 'flex-end',
marginBottom: theme.spacing(),
[theme.breakpoints.only('sm')]: {
marginRight: theme.spacing(),
},
}));

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface Drawer {
ruleIdx?: number;
}

const FirewallRulesLanding = (props: Props) => {
export const FirewallRulesLanding = React.memo((props: Props) => {
const { disabled, firewallID, rules } = props;
const { mutateAsync: updateFirewallRules } = useUpdateFirewallRulesMutation(
firewallID
Expand Down Expand Up @@ -373,7 +373,7 @@ const FirewallRulesLanding = (props: Props) => {
/>
</>
);
};
});

const StyledActionsPanel = styled(ActionsPanel, {
label: 'StyledActionsPanel',
Expand All @@ -386,8 +386,6 @@ const StyledDiv = styled('div', { label: 'StyledDiv' })(({ theme }) => ({
marginTop: theme.spacing(2),
}));

export default React.memo(FirewallRulesLanding);

interface DiscardChangesDialogProps {
handleClose: () => void;
handleDiscard: () => void;
Expand Down
Loading