Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions app/forms/instance-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { RadioCard } from '~/ui/lib/Radio'
import { Slash } from '~/ui/lib/Slash'
import { Tabs } from '~/ui/lib/Tabs'
import { TextInputHint } from '~/ui/lib/TextInput'
import { HintLink, TextInputHint } from '~/ui/lib/TextInput'
import { TipIcon } from '~/ui/lib/TipIcon'
import { Tooltip } from '~/ui/lib/Tooltip'
import { Wrap } from '~/ui/util/wrap'
Expand Down Expand Up @@ -1122,12 +1122,8 @@ const PRESETS = [
const UserDataDescription = () => (
<>
Data or scripts to be passed to cloud-init as{' '}
<a href={links.cloudInitFormat} target="_blank" rel="noreferrer">
user data
</a>{' '}
<a href={links.cloudInitExamples} target="_blank" rel="noreferrer">
(examples)
</a>{' '}
if the selected boot image supports it. Maximum size 32 KiB.
<HintLink href={links.cloudInitFormat}>user data</HintLink>{' '}
<HintLink href={links.cloudInitExamples}>(examples)</HintLink> if the selected boot
image supports it. Maximum size 32 KiB.
</>
)
36 changes: 16 additions & 20 deletions app/forms/network-interface-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { match } from 'ts-pattern'

import {
api,
Expand All @@ -28,7 +29,7 @@ import { FieldLabel } from '~/ui/lib/FieldLabel'
import { Message } from '~/ui/lib/Message'
import { ClearAndAddButtons, MiniTable } from '~/ui/lib/MiniTable'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { TextInputHint } from '~/ui/lib/TextInput'
import { HintLink, TextInputHint } from '~/ui/lib/TextInput'
import { KEYS } from '~/ui/util/keys'
import { parseIpNet, validateIpNet } from '~/util/ip'
import { docLinks, links } from '~/util/links'
Expand Down Expand Up @@ -70,16 +71,14 @@ export function EditNetworkInterfaceForm({

// Determine what IP versions this NIC supports
const { ipStack } = editing
const supportsV4 = ipStack.type === 'v4' || ipStack.type === 'dual_stack'
const supportsV6 = ipStack.type === 'v6' || ipStack.type === 'dual_stack'
const supportedVersions =
supportsV4 && supportsV6 ? 'both IPv4 and IPv6' : supportsV4 ? 'IPv4' : 'IPv6'
const exampleIPs =
supportsV4 && supportsV6
? '192.168.0.0/16 or fd00::/64'
: supportsV4
? '192.168.0.0/16'
: 'fd00::/64'
const { supportedVersions, exampleIPs } = match(ipStack.type)
.with('v4', () => ({ supportedVersions: 'IPv4', exampleIPs: '192.168.0.0/16' }))
.with('v6', () => ({ supportedVersions: 'IPv6', exampleIPs: 'fd00::/64' }))
.with('dual_stack', () => ({
supportedVersions: 'both IPv4 and IPv6',
exampleIPs: '192.168.0.0/16 or fd00::/64',
}))
.exhaustive()

const transitIpsForm = useForm({ defaultValues: { transitIp: '' } })
const transitIpValue = transitIpsForm.watch('transitIp')
Expand Down Expand Up @@ -123,11 +122,8 @@ export function EditNetworkInterfaceForm({
Transit IPs
</FieldLabel>
<TextInputHint id="transitIp-help-text" className="mb-2">
An IP network, like {exampleIPs}.
<br />
<a href={links.transitIpsDocs} target="_blank" rel="noreferrer">
Learn more about transit IPs.
</a>
These allow an instance to opt into traffic from a wider address range. Learn
more in the <HintLink href={links.transitIpsDocs}>Networking</HintLink> guide.
</TextInputHint>
<TextFieldInner
id="transitIp"
Expand All @@ -143,18 +139,18 @@ export function EditNetworkInterfaceForm({
const error = validateIpNet(value)
if (error) return error

// Check if Transit IP version matches NIC's supported versions
// Check if transit IP version matches NIC's stack type
const parsed = parseIpNet(value)
if (parsed.type === 'v4' && !supportsV4) {
if (parsed.type === 'v4' && ipStack.type === 'v6') {
return 'IPv4 transit IP not supported by this network interface'
}
if (parsed.type === 'v6' && !supportsV6) {
if (parsed.type === 'v6' && ipStack.type === 'v4') {
return 'IPv6 transit IP not supported by this network interface'
}

if (transitIps.includes(value)) return 'Transit IP already in list'
}}
placeholder="Enter an IP network"
placeholder={`An IP network, e.g., ${exampleIPs}`}
/>
</div>
<ClearAndAddButtons
Expand Down
17 changes: 10 additions & 7 deletions app/ui/lib/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,20 @@ type HintProps = {
* Pass id here and include that ID in aria-describedby on the TextField
*/
export const TextInputHint = ({ id, children, className }: HintProps) => (
<div
id={id}
className={cn(
'text-sans-sm text-secondary hover:[&_>_a]:text-raise mt-1 [&_>_a]:underline',
className
)}
>
<div id={id} className={cn('text-sans-sm text-secondary mt-1', className)}>
{children}
</div>
)

export type HintLinkProps = { href: string; children: React.ReactNode }

/** External link styled for use inside a TextInputHint */
export const HintLink = ({ href, children }: HintLinkProps) => (
<a href={href} target="_blank" rel="noreferrer" className="hover:text-raise underline">
{children}
</a>
)

export const TextInputError = ({ children }: { children: string }) => {
useEffect(() => announce(children, 'assertive'), [children])
return <div className="text-sans-md text-destructive ml-px py-2">{children}</div>
Expand Down
Loading