diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index c44efe44dc..8ec8c6716d 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -7,7 +7,7 @@ */ import { useState } from 'react' import { useForm } from 'react-hook-form' -import { Link, Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' +import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, @@ -19,6 +19,7 @@ import { } from '@oxide/api' import { IpGlobal24Icon, Networking24Icon } from '@oxide/design-system/icons/react' +import { ExternalLink } from '~/components/ExternalLink' import { HL } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks' import { confirmAction } from '~/stores/confirm-action' @@ -27,13 +28,13 @@ import { addToast } from '~/stores/toast' import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell' import type { MenuAction } from '~/table/columns/action-col' import { useQueryTable } from '~/table/QueryTable' -import { buttonStyle } from '~/ui/lib/Button' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { Listbox } from '~/ui/lib/Listbox' import { Message } from '~/ui/lib/Message' import { Modal } from '~/ui/lib/Modal' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' -import { TableActions } from '~/ui/lib/Table' +import { TableControls, TableControlsLink, TableControlsText } from '~/ui/lib/Table' +import { links } from '~/util/links' import { pb } from '~/util/path-builder' const EmptyState = () => ( @@ -161,11 +162,16 @@ export function FloatingIpsPage() { }>Floating IPs - - + + + Floating IPs are public IP addresses that can be attached to instances. They allow + your instances to be reachable from the internet. Find out more about{' '} + managing floating IPs. + + New Floating IP - - + + } makeActions={makeActions}> diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index b64b777dcd..292769dd3d 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -33,11 +33,12 @@ import { LinkCell } from '~/table/cells/LinkCell' import type { MenuAction } from '~/table/columns/action-col' import { useQueryTable } from '~/table/QueryTable' import { Badge } from '~/ui/lib/Badge' -import { Button, buttonStyle } from '~/ui/lib/Button' +import { buttonStyle } from '~/ui/lib/Button' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { Message } from '~/ui/lib/Message' import { Modal } from '~/ui/lib/Modal' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' +import { TableControls, TableControlsButton, TableControlsText } from '~/ui/lib/Table' import { Tabs } from '~/ui/lib/Tabs' import { links } from '~/util/links' import { pb } from '~/util/path-builder' @@ -216,18 +217,18 @@ function LinkedSilosTable() { return ( <> -
-

+ + Users in linked silos can allocate external IPs from this pool for their instances. A silo can have at most one default pool. IPs are allocated from the default pool when users ask for one without specifying a pool. Read the docs to learn more about{' '} managing IP pools. -

- -
+ +
-
-

+ + Users in this silo can allocate external IPs from these pools for their instances. A silo can have at most one default pool. IPs are allocated from the default pool when users ask for one without specifying a pool. Read the docs to learn more about managing IP pools. -

- -
+ +
} makeActions={makeActions}> pb.ipPool({ pool }))} /> diff --git a/app/ui/lib/Button.tsx b/app/ui/lib/Button.tsx index 6c7c627e4c..ae972219b0 100644 --- a/app/ui/lib/Button.tsx +++ b/app/ui/lib/Button.tsx @@ -35,7 +35,7 @@ export const buttonStyle = ({ variant = 'primary', }: ButtonStyleProps = {}) => { return cn( - 'ox-button elevation-1 rounded inline-flex items-center justify-center align-top disabled:cursor-not-allowed', + 'ox-button elevation-1 rounded inline-flex items-center justify-center align-top disabled:cursor-not-allowed shrink-0', `btn-${variant}`, sizeStyle[size], variant === 'danger' diff --git a/app/ui/lib/Table.tsx b/app/ui/lib/Table.tsx index 4f3014943d..08c88239e5 100644 --- a/app/ui/lib/Table.tsx +++ b/app/ui/lib/Table.tsx @@ -7,9 +7,11 @@ */ import cn from 'classnames' import React, { useRef, type ReactElement } from 'react' +import { Link, type LinkProps } from 'react-router-dom' import SimpleBar from 'simplebar-react' import { useIsOverflow } from '~/hooks' +import { Button, buttonStyle, type ButtonProps } from '~/ui/lib/Button' import { classed } from '~/util/classed' export type TableProps = JSX.IntrinsicElements['table'] @@ -124,3 +126,17 @@ Table.Cell = ({ height = 'large', className, children, ...props }: TableCellProp export const TableActions = classed.div`-mt-11 mb-3 flex justify-end space-x-2` export const TableEmptyBox = classed.div`flex h-full max-h-[480px] items-center justify-center rounded-lg border border-secondary p-4` + +/** + * Used _outside_ of the `Table`, this element includes a soon-to-be-removed description of the resource inside the table, + * along with a link to more info, and a button to take action on the resource listed in the table. + */ +export const TableControls = classed.div`mb-4 flex items-end justify-between space-x-8` +export const TableControlsText = classed.p`max-w-2xl text-sans-md text-secondary` + +export const TableControlsButton = (props: ButtonProps) => ( +