-
Notifications
You must be signed in to change notification settings - Fork 21
Create Floating IPs and attach them to instances #1957
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
Merged
Merged
Changes from all commits
Commits
Show all changes
69 commits
Select commit
Hold shift + click to select a range
0e8f468
Add Floating IP mock API calls
charliepark 458187d
Working on networking / routes for pages and tabs
charliepark 6bd6c14
Adjusting from VPC base to Floating IP implementation
charliepark 187e04b
Adjust vertical spacing on tabbed pages with table action buttons
charliepark f12e50c
Add Floating IP to mock API calls
charliepark 6722664
Update columns
charliepark a5ea03a
Add form for Floating IP create
charliepark bdad833
Update actions list
charliepark 717b967
Get routing on floatingIpView working, though will remove it soon
charliepark 86b7fcb
Get attached instance name into Floating IPs table
charliepark 4a4d813
Get pools loading in Floating IP create form
charliepark 6b00cd2
Add IP Address field validation
charliepark 2342a61
Update formatting on dropdown
charliepark 442f05e
Merge branch 'main' into floating-ips
charliepark bb587e3
Refactor validation
charliepark b1c45ab
Pass address to ip
charliepark d37b9a7
Default to vpcs page
charliepark 401fa4b
Proper logic for disabling detach/delete actions
charliepark 591f10b
Enable deleting of Floating IPs
charliepark 61262c6
FloatingIp sidebar loading, but not saving yet
charliepark ec0e9fc
Merge branch 'main' into floating-ips
charliepark 22b5927
Refactor
charliepark 0887393
working form in side modal to attach to instances
charliepark 7bf2bdc
Cleaning up Floating IPs page
charliepark 3036579
Merge branch 'main' into floating-ips
charliepark 89e5125
Detaching via modal works
charliepark 0662b94
Refactor
charliepark 6cd3883
Attach Floating IP to Instance working
charliepark 421a144
Spoof ip
charliepark 70c80f5
Resolve TS issues
charliepark 7f97a4c
Merge main
charliepark 0ebb041
Remove js files accidentally included in branch
charliepark 0504fb3
Refactor deletion flow
charliepark 999c3ac
Refactor form
charliepark e6a5acd
Merge branch 'main' into floating-ips
charliepark b57a539
Fix import to align with new rule
charliepark 2dc8bd7
Moving Floating IPs to project resource level
charliepark 87a3a0d
Clean up unneeded files
charliepark a76767b
Refactor - Networking page not needed at all
charliepark 7e1d683
Clean up redirect reference
charliepark f70c63f
Consistency on route naming
charliepark 7a8a31f
One more consistency fix
charliepark 052ec9c
Make the linter happy
charliepark b7bdc0e
Update routes so Floating IP page doesn't disappear when using create…
charliepark a47d556
Fix e2e test
charliepark b744d63
Update libs/api-mocks/msw/handlers.ts
charliepark 433d6a1
Add InstanceLinkCell component
charliepark af2741d
Move detach flow to action menu
charliepark 08c654d
Refactor loader to get instance list loading appropriately
charliepark 5756309
Remove unneeded file
charliepark 0df20f1
Refactor props for floating ip create form
charliepark c7af4dc
Align path for floatingIpNew with standard syntax
charliepark b700959
Use floatingIp lookup
charliepark 10bce52
Make sure an already-attached floating IP can't be attached again
charliepark 77246eb
Update type on Floating IP Create - address value, and prevent spaces…
charliepark c9134af
Refactor forms and types
charliepark c148a98
Refactor
charliepark dae7785
Accordion to hide advanced settings; refactorable
charliepark 704eade
add e2e test for creating a floating ip; attach / detach still in pro…
charliepark 3773c54
Attacah/Detach test passes
charliepark 12c2441
Extract simplified AccordionItem component
charliepark d508956
Pull advanced props over to AccordionItem so we can reuse it wherever…
charliepark 8b0b87e
Updated test with helper utils
charliepark 7868586
Revert Playwright utils
charliepark 3195eb1
Last util cleanup
charliepark 0f9dd2f
Fix Playwright test
charliepark 1f3bf52
Add info box to attach modal; update action menu to have either attac…
charliepark 2a17795
No period for single-line Message
charliepark 353ee84
Refactor useState flow to more intelligently use null
charliepark File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,46 @@ | ||
| /* | ||
| * This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
| * | ||
| * Copyright Oxide Computer Company | ||
| */ | ||
| import * as Accordion from '@radix-ui/react-accordion' | ||
| import cn from 'classnames' | ||
| import { useEffect, useRef } from 'react' | ||
|
|
||
| import { DirectionRightIcon } from '@oxide/design-system/icons/react' | ||
|
|
||
| type AccordionItemProps = { | ||
| children: React.ReactNode | ||
| isOpen: boolean | ||
| label: string | ||
| value: string | ||
| } | ||
|
|
||
| export const AccordionItem = ({ children, isOpen, label, value }: AccordionItemProps) => { | ||
| const contentRef = useRef<HTMLDivElement>(null) | ||
| useEffect(() => { | ||
| if (isOpen && contentRef.current) { | ||
| contentRef.current.scrollIntoView({ behavior: 'smooth' }) | ||
| } | ||
| }, [isOpen]) | ||
|
|
||
| return ( | ||
| <Accordion.Item value={value}> | ||
| <Accordion.Header className="max-w-lg"> | ||
| <Accordion.Trigger className="group flex w-full items-center justify-between border-t pt-2 text-sans-xl border-secondary [&>svg]:data-[state=open]:rotate-90"> | ||
| <div className="text-secondary">{label}</div> | ||
| <DirectionRightIcon className="transition-all text-secondary" /> | ||
| </Accordion.Trigger> | ||
| </Accordion.Header> | ||
| <Accordion.Content | ||
| ref={contentRef} | ||
| forceMount | ||
| className={cn('ox-accordion-content overflow-hidden py-8', { hidden: !isOpen })} | ||
| > | ||
| {children} | ||
| </Accordion.Content> | ||
| </Accordion.Item> | ||
| ) | ||
| } |
This file contains hidden or 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,147 @@ | ||
| /* | ||
| * This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
| * | ||
| * Copyright Oxide Computer Company | ||
| */ | ||
| import * as Accordion from '@radix-ui/react-accordion' | ||
| import { useState } from 'react' | ||
| import { useNavigate } from 'react-router-dom' | ||
| import type { SetRequired } from 'type-fest' | ||
|
|
||
| import { | ||
| apiQueryClient, | ||
| useApiMutation, | ||
| useApiQueryClient, | ||
| usePrefetchedApiQuery, | ||
| type FloatingIpCreate, | ||
| type SiloIpPool, | ||
| } from '@oxide/api' | ||
| import { Badge, Message } from '@oxide/ui' | ||
| import { validateIp } from '@oxide/util' | ||
|
|
||
| import { AccordionItem } from 'app/components/AccordionItem' | ||
| import { | ||
| DescriptionField, | ||
| ListboxField, | ||
| NameField, | ||
| SideModalForm, | ||
| TextField, | ||
| } from 'app/components/form' | ||
| import { useForm, useProjectSelector, useToast } from 'app/hooks' | ||
| import { pb } from 'app/util/path-builder' | ||
|
|
||
| CreateFloatingIpSideModalForm.loader = async () => { | ||
| await apiQueryClient.prefetchQuery('projectIpPoolList', { | ||
| query: { limit: 1000 }, | ||
| }) | ||
| return null | ||
| } | ||
|
|
||
| const toListboxItem = (p: SiloIpPool) => { | ||
| if (!p.isDefault) { | ||
| return { value: p.name, label: p.name } | ||
| } | ||
| // For the default pool, add a label to the dropdown | ||
| return { | ||
| value: p.name, | ||
| labelString: p.name, | ||
| label: ( | ||
| <> | ||
| {p.name}{' '} | ||
| <Badge className="ml-1" color="neutral"> | ||
| default | ||
| </Badge> | ||
| </> | ||
| ), | ||
| } | ||
| } | ||
|
|
||
| const defaultValues: SetRequired<FloatingIpCreate, 'address'> = { | ||
| name: '', | ||
| description: '', | ||
| pool: undefined, | ||
| address: '', | ||
| } | ||
|
|
||
| export function CreateFloatingIpSideModalForm() { | ||
| // Fetch 1000 to we can be sure to get them all. | ||
| const { data: allPools } = usePrefetchedApiQuery('projectIpPoolList', { | ||
| query: { limit: 1000 }, | ||
| }) | ||
charliepark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const queryClient = useApiQueryClient() | ||
| const projectSelector = useProjectSelector() | ||
| const addToast = useToast() | ||
| const navigate = useNavigate() | ||
|
|
||
| const createFloatingIp = useApiMutation('floatingIpCreate', { | ||
| onSuccess() { | ||
| queryClient.invalidateQueries('floatingIpList') | ||
| addToast({ content: 'Your Floating IP has been created' }) | ||
| navigate(pb.floatingIps(projectSelector)) | ||
| }, | ||
| }) | ||
|
|
||
| const form = useForm({ defaultValues }) | ||
| const isPoolSelected = !!form.watch('pool') | ||
|
|
||
| const [openItems, setOpenItems] = useState<string[]>([]) | ||
|
|
||
| return ( | ||
| <SideModalForm | ||
| id="create-floating-ip-form" | ||
| title="Create Floating IP" | ||
| form={form} | ||
| onDismiss={() => navigate(pb.floatingIps(projectSelector))} | ||
| onSubmit={({ address, ...rest }) => { | ||
| createFloatingIp.mutate({ | ||
| query: projectSelector, | ||
| // if address is '', evaluate as false and send as undefined | ||
| body: { address: address || undefined, ...rest }, | ||
| }) | ||
| }} | ||
| loading={createFloatingIp.isPending} | ||
| submitError={createFloatingIp.error} | ||
| > | ||
| <NameField name="name" control={form.control} /> | ||
| <DescriptionField name="description" control={form.control} /> | ||
|
|
||
| <Accordion.Root | ||
| type="multiple" | ||
| className="mt-12 max-w-lg" | ||
| value={openItems} | ||
| onValueChange={setOpenItems} | ||
| > | ||
| <AccordionItem | ||
| isOpen={openItems.includes('advanced')} | ||
| label="Advanced" | ||
| value="advanced" | ||
| > | ||
| <Message | ||
| variant="info" | ||
| content="If you don’t specify a pool, the default will be used" | ||
| /> | ||
|
|
||
| <ListboxField | ||
| name="pool" | ||
| items={allPools.items.map((p) => toListboxItem(p))} | ||
| label="IP pool" | ||
| control={form.control} | ||
| placeholder="Select pool" | ||
| /> | ||
| <TextField | ||
| name="address" | ||
| control={form.control} | ||
| disabled={!isPoolSelected} | ||
| transform={(v) => v.replace(/\s/g, '')} | ||
| validate={(ip) => | ||
| ip && !validateIp(ip).valid ? 'Not a valid IP address' : true | ||
| } | ||
| /> | ||
| </AccordionItem> | ||
| </Accordion.Root> | ||
| </SideModalForm> | ||
| ) | ||
| } | ||
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.