Skip to content

Commit 57ed587

Browse files
settings modals
1 parent 2780708 commit 57ed587

File tree

12 files changed

+201
-73
lines changed

12 files changed

+201
-73
lines changed

packages/manager/src/features/Firewalls/FirewallDetail/Devices/RemoveDeviceDialog.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,50 @@
1+
import {
2+
linodeQueries,
3+
nodebalancerQueries,
4+
useRemoveFirewallDeviceMutation,
5+
} from '@linode/queries';
16
import { ActionsPanel, Typography } from '@linode/ui';
27
import { useQueryClient } from '@tanstack/react-query';
38
import { useSnackbar } from 'notistack';
49
import * as React from 'react';
510

611
import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
7-
import {
8-
useRemoveFirewallDeviceMutation,
9-
linodeQueries,
10-
nodebalancerQueries,
11-
} from '@linode/queries';
1212

1313
import type { FirewallDevice } from '@linode/api-v4';
1414

1515
export interface Props {
16-
device: FirewallDevice | undefined;
16+
device?: FirewallDevice | undefined;
17+
devices?: FirewallDevice[] | undefined;
1718
firewallId: number;
1819
firewallLabel: string;
20+
isFetching?: boolean;
21+
nodeBalancerId?: number;
1922
onClose: () => void;
2023
onService: boolean | undefined;
2124
open: boolean;
2225
}
2326

2427
export const RemoveDeviceDialog = React.memo((props: Props) => {
25-
const { device, firewallId, firewallLabel, onClose, onService, open } = props;
28+
const {
29+
device: _device,
30+
devices,
31+
firewallId,
32+
firewallLabel,
33+
isFetching,
34+
nodeBalancerId,
35+
onClose,
36+
onService,
37+
open,
38+
} = props;
2639

2740
const { enqueueSnackbar } = useSnackbar();
41+
const device =
42+
_device ??
43+
devices?.find(
44+
(device) =>
45+
device.entity.type === 'nodebalancer' &&
46+
device.entity.id === nodeBalancerId
47+
);
2848
const deviceType = device?.entity.type;
2949

3050
const { error, isPending, mutateAsync } = useRemoveFirewallDeviceMutation(
@@ -105,6 +125,7 @@ export const RemoveDeviceDialog = React.memo((props: Props) => {
105125
/>
106126
}
107127
error={error?.[0]?.reason}
128+
isFetching={isFetching}
108129
onClose={onClose}
109130
open={open}
110131
title={dialogTitle}

packages/manager/src/features/NodeBalancers/NodeBalancerDeleteDialog.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const preference: ManagerPreferences['type_to_confirm'] = true;
1818

1919
const navigate = vi.fn();
2020
const queryMocks = vi.hoisted(() => ({
21+
useMatch: vi.fn(() => ({})),
2122
useNavigate: vi.fn(() => navigate),
2223
usePreferences: vi.fn().mockReturnValue({}),
2324
}));
@@ -34,6 +35,7 @@ vi.mock('@tanstack/react-router', async () => {
3435
const actual = await vi.importActual('@tanstack/react-router');
3536
return {
3637
...actual,
38+
useMatch: queryMocks.useMatch,
3739
useNavigate: queryMocks.useNavigate,
3840
};
3941
});

packages/manager/src/features/NodeBalancers/NodeBalancerDeleteDialog.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useNodebalancerDeleteMutation } from '@linode/queries';
22
import { Notice, Typography } from '@linode/ui';
3-
import { useNavigate } from '@tanstack/react-router';
3+
import { useMatch, useNavigate } from '@tanstack/react-router';
44
import * as React from 'react';
55

66
import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog';
@@ -19,6 +19,9 @@ export const NodeBalancerDeleteDialog = ({
1919
selectedNodeBalancer,
2020
}: Props) => {
2121
const navigate = useNavigate();
22+
const match = useMatch({
23+
strict: false,
24+
});
2225
const { error, isPending, mutateAsync } = useNodebalancerDeleteMutation(
2326
selectedNodeBalancer?.id ?? -1
2427
);
@@ -38,12 +41,20 @@ export const NodeBalancerDeleteDialog = ({
3841
primaryBtnText: 'Delete',
3942
type: 'NodeBalancer',
4043
}}
44+
onClose={
45+
match.routeId === '/nodebalancers/$id/settings/delete'
46+
? () =>
47+
navigate({
48+
params: { id: String(selectedNodeBalancer?.id) },
49+
to: '/nodebalancers/$id/settings',
50+
})
51+
: () => navigate({ to: '/nodebalancers' })
52+
}
4153
errors={error ?? undefined}
4254
expand
4355
label={'NodeBalancer Label'}
4456
loading={isPending || isFetching}
4557
onClick={onDelete}
46-
onClose={() => navigate({ to: '/nodebalancers' })}
4758
open={open}
4859
title={`Delete ${label}?`}
4960
typographyStyle={{ marginTop: '20px' }}

packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewalls.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ const firewall = firewallFactory.build({ label: 'mock-firewall-1' });
99

1010
// Set up various mocks for tests
1111

12+
const navigate = vi.fn();
1213
const queryMocks = vi.hoisted(() => ({
14+
useMatch: vi.fn(() => ({})),
15+
useNavigate: vi.fn(() => navigate),
1316
useNodeBalancersFirewallsQuery: vi.fn().mockReturnValue({ data: undefined }),
17+
useParams: vi.fn(() => ({})),
1418
}));
1519

1620
vi.mock('@linode/queries', async () => {
@@ -21,6 +25,16 @@ vi.mock('@linode/queries', async () => {
2125
};
2226
});
2327

28+
vi.mock('@tanstack/react-router', async () => {
29+
const actual = await vi.importActual('@tanstack/react-router');
30+
return {
31+
...actual,
32+
useMatch: queryMocks.useMatch,
33+
useNavigate: queryMocks.useNavigate,
34+
useParams: queryMocks.useParams,
35+
};
36+
});
37+
2438
const props = {
2539
displayFirewallInfoText: false,
2640
nodeBalancerId: 1,
@@ -32,6 +46,9 @@ describe('NodeBalancerFirewalls', () => {
3246
data: { data: [firewall] },
3347
isLoading: false,
3448
});
49+
queryMocks.useParams.mockReturnValue({
50+
id: '1',
51+
});
3552
});
3653

3754
afterEach(() => {

packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewalls.tsx

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import { useNodeBalancersFirewallsQuery } from '@linode/queries';
1+
import {
2+
useAllFirewallDevicesQuery,
3+
useFirewallQuery,
4+
useNodeBalancersFirewallsQuery,
5+
} from '@linode/queries';
26
import { Box, Button, Stack, Typography } from '@linode/ui';
7+
import { useMatch, useNavigate } from '@tanstack/react-router';
38
import React from 'react';
49

510
import { Drawer } from 'src/components/Drawer';
@@ -14,18 +19,22 @@ import { TableRowError } from 'src/components/TableRowError/TableRowError';
1419
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
1520
import { RemoveDeviceDialog } from 'src/features/Firewalls/FirewallDetail/Devices/RemoveDeviceDialog';
1621
import { AddFirewallForm } from 'src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeFirewalls/AddFirewallForm';
22+
import { useDialogData } from 'src/hooks/useDialogData';
1723

1824
import { NodeBalancerFirewallsRow } from './NodeBalancerFirewallsRow';
1925

20-
import type { Firewall, FirewallDevice } from '@linode/api-v4';
26+
import type { Firewall } from '@linode/api-v4';
2127

2228
interface Props {
2329
nodeBalancerId: number;
2430
}
2531

2632
export const NodeBalancerFirewalls = (props: Props) => {
2733
const { nodeBalancerId } = props;
28-
34+
const navigate = useNavigate();
35+
const match = useMatch({
36+
strict: false,
37+
});
2938
const {
3039
data: attachedFirewallData,
3140
error,
@@ -34,27 +43,35 @@ export const NodeBalancerFirewalls = (props: Props) => {
3443

3544
const attachedFirewalls = attachedFirewallData?.data;
3645

37-
const [selectedFirewall, setSelectedFirewall] = React.useState<Firewall>();
38-
39-
const [
40-
deviceToBeRemoved,
41-
setDeviceToBeRemoved,
42-
] = React.useState<FirewallDevice>();
43-
44-
const [
45-
isRemoveDeviceDialogOpen,
46-
setIsRemoveDeviceDialogOpen,
47-
] = React.useState<boolean>(false);
46+
const isUnassignFirewallRoute =
47+
match.routeId ===
48+
'/nodebalancers/$id/settings/unassign-firewall/$firewallId';
4849

49-
const [
50-
isAddFirewallDrawerOpen,
51-
setIsAddFirewalDrawerOpen,
52-
] = React.useState<boolean>(false);
53-
54-
const handleClickUnassign = (device: FirewallDevice, firewall: Firewall) => {
55-
setDeviceToBeRemoved(device);
56-
setSelectedFirewall(firewall);
57-
setIsRemoveDeviceDialogOpen(true);
50+
const {
51+
data: selectedFirewall,
52+
isFetching: isFetchingSelectedFirewall,
53+
} = useDialogData({
54+
enabled: isUnassignFirewallRoute,
55+
paramKey: 'firewallId',
56+
queryHook: useFirewallQuery,
57+
redirectToOnNotFound: '/nodebalancers/$id/settings',
58+
});
59+
60+
const { data: devices, isFetching: isFetchingDevices } = useDialogData({
61+
enabled: isUnassignFirewallRoute,
62+
paramKey: 'firewallId',
63+
queryHook: useAllFirewallDevicesQuery,
64+
redirectToOnNotFound: '/nodebalancers/$id/settings',
65+
});
66+
67+
const handleClickUnassign = (firewall: Firewall) => {
68+
navigate({
69+
params: {
70+
firewallId: String(firewall.id),
71+
id: String(nodeBalancerId),
72+
},
73+
to: '/nodebalancers/$id/settings/unassign-firewall/$firewallId',
74+
});
5875
};
5976

6077
const renderTableContent = () => {
@@ -72,10 +89,11 @@ export const NodeBalancerFirewalls = (props: Props) => {
7289

7390
return attachedFirewalls.map((attachedFirewall) => (
7491
<NodeBalancerFirewallsRow
92+
devices={devices}
7593
firewall={attachedFirewall}
7694
key={`firewall-${attachedFirewall.id}`}
77-
nodeBalancerID={nodeBalancerId}
78-
onClickUnassign={handleClickUnassign}
95+
nodeBalancerId={nodeBalancerId}
96+
onClickUnassign={() => handleClickUnassign(attachedFirewall)}
7997
/>
8098
));
8199
};
@@ -94,9 +112,14 @@ export const NodeBalancerFirewalls = (props: Props) => {
94112
to your NodeBalancer. Only inbound rules are applied to NodeBalancers.
95113
</Typography>
96114
<Button
115+
onClick={() =>
116+
navigate({
117+
params: { id: String(nodeBalancerId) },
118+
to: '/nodebalancers/$id/settings/add-firewall',
119+
})
120+
}
97121
buttonType="primary"
98122
disabled={attachedFirewallData && attachedFirewallData.results >= 1}
99-
onClick={() => setIsAddFirewalDrawerOpen(true)}
100123
tooltipText="NodeBalanacers can only have one Firewall assigned."
101124
>
102125
Add Firewall
@@ -114,22 +137,42 @@ export const NodeBalancerFirewalls = (props: Props) => {
114137
<TableBody>{renderTableContent()}</TableBody>
115138
</Table>
116139
<RemoveDeviceDialog
117-
device={deviceToBeRemoved}
140+
onClose={() =>
141+
navigate({
142+
params: { id: String(nodeBalancerId) },
143+
to: '/nodebalancers/$id/settings',
144+
})
145+
}
146+
open={
147+
match.routeId ===
148+
'/nodebalancers/$id/settings/unassign-firewall/$firewallId'
149+
}
150+
devices={devices}
118151
firewallId={selectedFirewall?.id ?? -1}
119152
firewallLabel={selectedFirewall?.label ?? ''}
120-
onClose={() => setIsRemoveDeviceDialogOpen(false)}
153+
isFetching={isFetchingDevices || isFetchingSelectedFirewall}
154+
nodeBalancerId={nodeBalancerId}
121155
onService
122-
open={isRemoveDeviceDialogOpen}
123156
/>
124157
<Drawer
125-
onClose={() => setIsAddFirewalDrawerOpen(false)}
126-
open={isAddFirewallDrawerOpen}
158+
onClose={() =>
159+
navigate({
160+
params: { id: String(nodeBalancerId) },
161+
to: '/nodebalancers/$id/settings',
162+
})
163+
}
164+
open={match.routeId === '/nodebalancers/$id/settings/add-firewall'}
127165
title="Add Firewall"
128166
>
129167
<AddFirewallForm
168+
onCancel={() =>
169+
navigate({
170+
params: { id: String(nodeBalancerId) },
171+
to: '/nodebalancers/$id/settings',
172+
})
173+
}
130174
entityId={nodeBalancerId}
131175
entityType="nodebalancer"
132-
onCancel={() => setIsAddFirewalDrawerOpen(false)}
133176
/>
134177
</Drawer>
135178
</Stack>

packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewallsRow.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ vi.mock('@linode/queries', async () => {
2525
});
2626

2727
const props = {
28+
devices: [],
2829
firewall,
29-
nodeBalancerID: 1,
30+
nodeBalancerId: 1,
3031
onClickUnassign: vi.fn(),
3132
};
3233

packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewallsRow.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useAllFirewallDevicesQuery } from '@linode/queries';
21
import { capitalize } from '@linode/utilities';
32
import * as React from 'react';
43

@@ -16,27 +15,17 @@ import { NodeBalancerFirewallsActionMenu } from './NodeBalancerFirewallsActionMe
1615
import type { Firewall, FirewallDevice } from '@linode/api-v4';
1716

1817
interface Props {
18+
devices: FirewallDevice[] | undefined;
1919
firewall: Firewall;
20-
nodeBalancerID: number;
21-
onClickUnassign: (
22-
device: FirewallDevice | undefined,
23-
firewall: Firewall
24-
) => void;
20+
nodeBalancerId: number;
21+
onClickUnassign: () => void;
2522
}
2623

2724
export const NodeBalancerFirewallsRow = (props: Props) => {
28-
const { firewall, nodeBalancerID, onClickUnassign } = props;
25+
const { firewall, onClickUnassign } = props;
2926

3027
const { id: firewallID, label, rules, status } = firewall;
3128

32-
const { data: devices } = useAllFirewallDevicesQuery(firewallID);
33-
34-
const firewallDevice = devices?.find(
35-
(device) =>
36-
device.entity.type === 'nodebalancer' &&
37-
device.entity.id === nodeBalancerID
38-
);
39-
4029
const count = getCountOfRules(rules);
4130

4231
return (
@@ -54,7 +43,7 @@ export const NodeBalancerFirewallsRow = (props: Props) => {
5443
<TableCell actionCell>
5544
<NodeBalancerFirewallsActionMenu
5645
firewallID={firewallID}
57-
onUnassign={() => onClickUnassign(firewallDevice, firewall)}
46+
onUnassign={onClickUnassign}
5847
/>
5948
</TableCell>
6049
</TableRow>

0 commit comments

Comments
 (0)