diff --git a/app/components/ExternalIps.tsx b/app/components/ExternalIps.tsx
index eb9e036932..f0a081666e 100644
--- a/app/components/ExternalIps.tsx
+++ b/app/components/ExternalIps.tsx
@@ -6,13 +6,22 @@
* Copyright Oxide Computer Company
*/
-import { useApiQuery } from '@oxide/api'
+import { Link } from 'react-router'
+import * as R from 'remeda'
+
+import { useApiQuery, type ExternalIp } from '@oxide/api'
import { EmptyCell, SkeletonCell } from '~/table/cells/EmptyCell'
import { CopyableIp } from '~/ui/lib/CopyableIp'
+import { Slash } from '~/ui/lib/Slash'
import { intersperse } from '~/util/array'
+import { pb } from '~/util/path-builder'
import type * as PP from '~/util/path-params'
+/** Move ephemeral IP (if present) to the end of the list of external IPs */
+export const orderIps = (ips: ExternalIp[]) =>
+ R.sortBy(ips, (a) => (a.kind === 'ephemeral' ? 1 : -1))
+
export function ExternalIps({ project, instance }: PP.Instance) {
const { data, isPending } = useApiQuery('instanceExternalIpList', {
path: { instance },
@@ -22,11 +31,27 @@ export function ExternalIps({ project, instance }: PP.Instance) {
const ips = data?.items
if (!ips || ips.length === 0) return
+ const orderedIps = orderIps(ips)
+ const ipsToShow = orderedIps.slice(0, 2)
+ const overflowCount = orderedIps.length - ipsToShow.length
+
+ // create a list of CopyableIp components
+ const links = ipsToShow.map((eip) => )
+
return (
-
- {intersperse(
- ips.map((eip) => ),
- /
+
+ {intersperse(links, )}
+ {/* if there are more than 2 ips, add a link to the instance networking page */}
+ {overflowCount > 0 && (
+ <>
+
+
+ …
+
+ >
)}
)
diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx
index 43e99b472f..de416e3242 100644
--- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx
+++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx
@@ -24,6 +24,7 @@ import { IpGlobal24Icon, Networking24Icon } from '@oxide/design-system/icons/rea
import { AttachEphemeralIpModal } from '~/components/AttachEphemeralIpModal'
import { AttachFloatingIpModal } from '~/components/AttachFloatingIpModal'
+import { orderIps } from '~/components/ExternalIps'
import { HL } from '~/components/HL'
import { ListPlusCell } from '~/components/ListPlusCell'
import { CreateNetworkInterfaceForm } from '~/forms/network-interface-create'
@@ -371,7 +372,7 @@ export function Component() {
const ipTableInstance = useReactTable({
columns: useColsWithActions(staticIpCols, makeIpActions),
- data: eips?.items || [],
+ data: useMemo(() => orderIps(eips.items), [eips]),
getCoreRowModel: getCoreRowModel(),
})
diff --git a/app/pages/settings/ProfilePage.tsx b/app/pages/settings/ProfilePage.tsx
index 8638c24c91..faec440179 100644
--- a/app/pages/settings/ProfilePage.tsx
+++ b/app/pages/settings/ProfilePage.tsx
@@ -45,7 +45,7 @@ export function ProfilePage() {
{me.displayName}
{me.id}
-
+
diff --git a/app/ui/lib/CopyableIp.tsx b/app/ui/lib/CopyableIp.tsx
index 61201b155b..6c0f522d81 100644
--- a/app/ui/lib/CopyableIp.tsx
+++ b/app/ui/lib/CopyableIp.tsx
@@ -8,7 +8,7 @@
import { CopyToClipboard } from '~/ui/lib/CopyToClipboard'
export const CopyableIp = ({ ip, isLinked = true }: { ip: string; isLinked?: boolean }) => (
-
+
{isLinked ? (
(
- /
+import cn from 'classnames'
+
+export const Slash = ({ className }: { className?: string }) => (
+
+ /
+
)
diff --git a/app/ui/lib/Truncate.tsx b/app/ui/lib/Truncate.tsx
index 92792b9813..b4dd18b464 100644
--- a/app/ui/lib/Truncate.tsx
+++ b/app/ui/lib/Truncate.tsx
@@ -33,7 +33,7 @@ export const Truncate = ({
// Only use the tooltip if the text is longer than maxLength
return (
// overflow-hidden required to make inner truncate work
-