-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [M3-7019] - Create add Nodebalancer to Firewall drawer (#9608)
* initial drawer commit * finished the add nodebalancer drawer * Added changeset: Created 'Add Nodebalancer' to Firewall drawer * updated drawer and started to add event handlers * added toast notification * added multiple toast notifications * separated nodebalancer and linode drawer * added infinite scrolling * fixed pr suggestions * partially eliminated type definitions in LinodeSelect * eliminated type definitions in LinodeSelect * changed type definition of onSelectionChange in NodeBalancerSelect * eliminated type declarations in NodeBalancerSelect * erased commented out code * initial round of fixes * fixed linode error drawer not closing * eliminated event message * added todo comment * added todo comment * fixed toast notification, error reset, and error text * fixed toast notification, error reset, and error text for Linode Drawer * fixed nodebalancer drawer error notices * merged with develop * initial migration to new autocomplete component, still some errors * can select linodes now, but the linodes arent showing as selected * fixed selection issue with autocomplete * migrated to new autocomplete component * remove NodeBalancerSelect file changes * added banks PR suggestions --------- Co-authored-by: TylerWJ <tylerwjones99@gmail.com>
- Loading branch information
1 parent
c5e03b9
commit cb13d24
Showing
12 changed files
with
506 additions
and
200 deletions.
There are no files selected for viewing
5 changes: 5 additions & 0 deletions
5
packages/manager/.changeset/pr-9608-upcoming-features-1693406491148.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Upcoming Features | ||
--- | ||
|
||
Created 'Add Nodebalancer' to Firewall drawer ([#9608](https://github.com/linode/manager/pull/9608)) |
161 changes: 0 additions & 161 deletions
161
packages/manager/src/features/Firewalls/FirewallDetail/Devices/AddDeviceDrawer.tsx
This file was deleted.
Oops, something went wrong.
211 changes: 211 additions & 0 deletions
211
packages/manager/src/features/Firewalls/FirewallDetail/Devices/AddLinodeDrawer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import { Linode } from '@linode/api-v4'; | ||
import { useSnackbar } from 'notistack'; | ||
import * as React from 'react'; | ||
import { useParams } from 'react-router-dom'; | ||
|
||
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; | ||
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; | ||
import { Drawer } from 'src/components/Drawer'; | ||
import { Link } from 'src/components/Link'; | ||
import { Notice } from 'src/components/Notice/Notice'; | ||
import { SupportLink } from 'src/components/SupportLink'; | ||
import { | ||
useAddFirewallDeviceMutation, | ||
useAllFirewallDevicesQuery, | ||
useFirewallQuery, | ||
} from 'src/queries/firewalls'; | ||
import { useAllLinodesQuery } from 'src/queries/linodes/linodes'; | ||
import { useGrants, useProfile } from 'src/queries/profile'; | ||
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; | ||
import { getEntityIdsByPermission } from 'src/utilities/grants'; | ||
|
||
interface Props { | ||
helperText: string; | ||
onClose: () => void; | ||
open: boolean; | ||
} | ||
|
||
export const AddLinodeDrawer = (props: Props) => { | ||
const { helperText, onClose, open } = props; | ||
|
||
const { id } = useParams<{ id: string }>(); | ||
|
||
const { enqueueSnackbar } = useSnackbar(); | ||
|
||
const { data: grants } = useGrants(); | ||
const { data: profile } = useProfile(); | ||
const isRestrictedUser = Boolean(profile?.restricted); | ||
|
||
const { data: firewall } = useFirewallQuery(Number(id)); | ||
const { | ||
data: currentDevices, | ||
isLoading: currentDevicesLoading, | ||
} = useAllFirewallDevicesQuery(Number(id)); | ||
|
||
const { isLoading, mutateAsync: addDevice } = useAddFirewallDeviceMutation( | ||
Number(id) | ||
); | ||
|
||
const [selectedLinodes, setSelectedLinodes] = React.useState<Linode[]>([]); | ||
|
||
const [localError, setLocalError] = React.useState<string | undefined>( | ||
undefined | ||
); | ||
|
||
const handleSubmit = async () => { | ||
let firstError: string | undefined = undefined; | ||
const failedLinodes: Linode[] = []; | ||
|
||
const results = await Promise.allSettled( | ||
selectedLinodes.map((linode) => | ||
addDevice({ id: linode.id, type: 'linode' }) | ||
) | ||
); | ||
|
||
results.forEach((result, index) => { | ||
const label = selectedLinodes[index].label; | ||
const id = selectedLinodes[index].id; | ||
if (result.status === 'fulfilled') { | ||
enqueueSnackbar(`${label} added successfully.`, { variant: 'success' }); | ||
} else { | ||
failedLinodes?.push(selectedLinodes[index]); | ||
const errorReason = getAPIErrorOrDefault( | ||
result.reason, | ||
`Failed to add Linode ${label} (ID ${id}).` | ||
)[0].reason; | ||
|
||
if (!firstError) { | ||
firstError = errorReason; | ||
} | ||
|
||
enqueueSnackbar(`Failed to add ${label}.`, { variant: 'error' }); | ||
} | ||
}); | ||
|
||
setLocalError(firstError); | ||
setSelectedLinodes(failedLinodes); | ||
|
||
if (!firstError) { | ||
onClose(); | ||
} | ||
}; | ||
|
||
const errorNotice = () => { | ||
let errorMsg = localError || ''; | ||
// match something like: Linode <linode_label> (ID <linode_id>) | ||
|
||
const linode = /Linode (.+?) \(ID ([^\)]+)\)/i.exec(errorMsg); | ||
const openTicket = errorMsg.match(/open a support ticket\./i); | ||
|
||
if (openTicket) { | ||
errorMsg = errorMsg.replace(/open a support ticket\./i, ''); | ||
} | ||
|
||
if (linode) { | ||
const [, label, id] = linode; | ||
|
||
// Break the errorMsg into two parts: before and after the linode pattern | ||
const startMsg = errorMsg.substring( | ||
0, | ||
errorMsg.indexOf(`Linode ${label}`) | ||
); | ||
const endMsg = errorMsg.substring( | ||
errorMsg.indexOf(`(ID ${id})`) + `(ID ${id})`.length | ||
); | ||
|
||
return ( | ||
<Notice | ||
sx={{ | ||
fontSize: '1rem', | ||
fontWeight: 'bold', | ||
lineHeight: '20px', | ||
}} | ||
variant="error" | ||
> | ||
{startMsg} | ||
<Link to={`/linodes/${id}`}>{label}</Link> | ||
{endMsg} | ||
{openTicket ? ( | ||
<> | ||
<SupportLink text="open a Support ticket" />. | ||
</> | ||
) : null} | ||
</Notice> | ||
); | ||
} else { | ||
return <Notice text={localError} variant="error" />; | ||
} | ||
}; | ||
|
||
const currentLinodeIds = | ||
currentDevices | ||
?.filter((device) => device.entity.type === 'linode') | ||
.map((device) => device.entity.id) ?? []; | ||
|
||
// If a user is restricted, they can not add a read-only Linode to a firewall. | ||
const readOnlyLinodeIds = isRestrictedUser | ||
? getEntityIdsByPermission(grants, 'linode', 'read_only') | ||
: []; | ||
|
||
const optionsFilter = (linode: Linode) => { | ||
return ![...currentLinodeIds, ...readOnlyLinodeIds].includes(linode.id); | ||
}; | ||
|
||
const { | ||
data, | ||
error: linodeError, | ||
isLoading: linodeIsLoading, | ||
} = useAllLinodesQuery(); | ||
|
||
React.useEffect(() => { | ||
if (linodeError) { | ||
setLocalError('Could not load Linode Data'); | ||
} | ||
}, [linodeError]); | ||
|
||
const linodes = data?.filter(optionsFilter); | ||
|
||
return ( | ||
<Drawer | ||
onClose={() => { | ||
setSelectedLinodes([]); | ||
setLocalError(undefined); | ||
onClose(); | ||
}} | ||
open={open} | ||
title={`Add Linode to Firewall: ${firewall?.label}`} | ||
> | ||
<form | ||
onSubmit={(e: React.ChangeEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
handleSubmit(); | ||
}} | ||
> | ||
{localError ? errorNotice() : null} | ||
<Autocomplete | ||
disabled={currentDevicesLoading || linodeIsLoading} | ||
helperText={helperText} | ||
label="Linodes" | ||
loading={currentDevicesLoading || linodeIsLoading} | ||
multiple | ||
noOptionsText="No Linodes available to add" | ||
onChange={(_, linodes) => setSelectedLinodes(linodes)} | ||
options={linodes || []} | ||
value={selectedLinodes} | ||
/> | ||
<ActionsPanel | ||
primaryButtonProps={{ | ||
disabled: selectedLinodes.length === 0, | ||
label: 'Add', | ||
loading: isLoading, | ||
onClick: handleSubmit, | ||
}} | ||
secondaryButtonProps={{ | ||
label: 'Cancel', | ||
onClick: onClose, | ||
}} | ||
/> | ||
</form> | ||
</Drawer> | ||
); | ||
}; |
Oops, something went wrong.