From ee0bf8602a712e98ad5d68b4b6f46255d50882eb Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Mon, 15 Jan 2024 12:19:55 +0000 Subject: [PATCH 01/16] Add VPC and instance external IP to instance page --- .../instances/instance/InstancePage.tsx | 31 +++++++++++++++++-- .../instances/instance/tabs/NetworkingTab.tsx | 31 ++++++++++++++----- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index 5a446332c4..890062f7ec 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -26,6 +26,7 @@ import { getInstanceSelector, useInstanceSelector, useQuickActions } from 'app/h import { pb } from 'app/util/path-builder' import { useMakeInstanceActions } from '../actions' +import { ExternalIpsFromInstanceName, VpcNameFromId } from './tabs/NetworkingTab' InstancePage.loader = async ({ params }: LoaderFunctionArgs) => { const { project, instance } = getInstanceSelector(params) @@ -33,6 +34,13 @@ InstancePage.loader = async ({ params }: LoaderFunctionArgs) => { path: { instance }, query: { project }, }) + await apiQueryClient.prefetchQuery('instanceNetworkInterfaceList', { + query: { project, instance, limit: 10 }, + }) + await apiQueryClient.prefetchQuery('instanceExternalIpList', { + path: { instance }, + query: { project }, + }) return null } @@ -54,6 +62,14 @@ export function InstancePage() { query: { project: instanceSelector.project }, }) + const { data: nicList } = usePrefetchedApiQuery('instanceNetworkInterfaceList', { + query: { + project: instanceSelector.project, + instance: instanceSelector.instance, + limit: 10, + }, + }) + const actions = useMemo( () => [ { @@ -81,6 +97,8 @@ export function InstancePage() { const memory = filesize(instance.memory, { output: 'object', base: 2 }) + const primaryVpcId = nicList.items && nicList.items.length > 0 && nicList.items[0].vpcId + return ( <> @@ -100,6 +118,11 @@ export function InstancePage() { + + + {primaryVpcId ? VpcNameFromId({ value: primaryVpcId }) : '-'} + + @@ -107,9 +130,6 @@ export function InstancePage() { - {/* - {instance.hostname || '–'} - */} {format(instance.timeCreated, 'MMM d, yyyy')}{' '} @@ -123,6 +143,11 @@ export function InstancePage() { {instance.id} + + + {ExternalIpsFromInstanceName({ value: true })} + + diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 218502e1a4..4bb80a5ce2 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -5,7 +5,7 @@ * * Copyright Oxide Computer Company */ -import { useState } from 'react' +import { Fragment, useState } from 'react' import { Link, type LoaderFunctionArgs } from 'react-router-dom' import { @@ -40,7 +40,7 @@ import { pb } from 'app/util/path-builder' import { fancifyStates } from './common' -const VpcNameFromId = ({ value }: { value: string }) => { +export const VpcNameFromId = ({ value }: { value: string }) => { const projectSelector = useProjectSelector() const { data: vpc, isError } = useApiQuery( 'vpcView', @@ -52,10 +52,11 @@ const VpcNameFromId = ({ value }: { value: string }) => { // possible because you can't delete a VPC that has child resources, but let's // be safe if (isError) return Deleted - if (!vpc) return // loading + if (!vpc) + return
// loading return ( {vpc.name} @@ -77,13 +78,29 @@ const SubnetNameFromId = ({ value }: { value: string }) => { return {subnet.name} } -function ExternalIpsFromInstanceName({ value: primary }: { value: boolean }) { +export function ExternalIpsFromInstanceName({ value: primary }: { value: boolean }) { const { project, instance } = useInstanceSelector() - const { data } = useApiQuery('instanceExternalIpList', { + const { data, isLoading } = useApiQuery('instanceExternalIpList', { path: { instance }, query: { project }, }) - const ips = data?.items.map((eip) => eip.ip).join(', ') + if (isLoading) + return
// loading + + const ips = data?.items.map((eip, index, array) => ( + + + {eip.ip} + + {index < array.length - 1 && ', '} + + )) + return {primary ? ips : <>—} } From 31c0d28039ccb34ddf3cf2a2366c550cc4d937d7 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Mon, 15 Jan 2024 12:31:11 +0000 Subject: [PATCH 02/16] Use slash as divider --- app/pages/project/instances/instance/tabs/NetworkingTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 4bb80a5ce2..1a08115bd5 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -97,7 +97,7 @@ export function ExternalIpsFromInstanceName({ value: primary }: { value: boolean > {eip.ip} - {index < array.length - 1 && ', '} + {index < array.length - 1 && / } )) From 4f8fc3fb6eaaa769c73c32dcea3e173142f0881b Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Mon, 15 Jan 2024 16:57:06 +0000 Subject: [PATCH 03/16] Adds copy button on instance external IP (#1885) * Update copy to clipboard component Uses new icon and snazzy animation * Add copy to clipboard to external IP * `CopyOnTruncate` component * Add description to `CopyOnTruncate` * Revert "Add description to `CopyOnTruncate`" This reverts commit 4bf0bcc5f33fc13936c5df5d0f9b4cf3693c3010. * Revert "`CopyOnTruncate` component" This reverts commit e4a010719aa02e45b1eb55157608240f05a6c590. --- .../instances/instance/InstancePage.tsx | 4 +-- .../instances/instance/tabs/NetworkingTab.tsx | 9 ++++- .../lib/copy-to-clipboard/CopyToClipboard.tsx | 33 +++++++++++++++---- package-lock.json | 14 ++++---- package.json | 2 +- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index 890062f7ec..f36c8c289c 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -144,9 +144,7 @@ export function InstancePage() { - - {ExternalIpsFromInstanceName({ value: true })} - + {ExternalIpsFromInstanceName({ value: true })} diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 1a08115bd5..62983f15dc 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -21,6 +21,7 @@ import { useQueryTable, type MenuAction } from '@oxide/table' import { Badge, Button, + CopyToClipboard, EmptyMessage, Networking24Icon, Spinner, @@ -101,7 +102,13 @@ export function ExternalIpsFromInstanceName({ value: primary }: { value: boolean )) - return {primary ? ips : <>—} + return ( +
+ {primary ? ips : <>—} + {/* If there's exactly one IP here, render a copy to clipboard button */} + {data?.items.length === 1 && } +
+ ) } NetworkingTab.loader = async ({ params }: LoaderFunctionArgs) => { diff --git a/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx b/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx index c521346eaf..2979d73629 100644 --- a/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx +++ b/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx @@ -6,9 +6,11 @@ * Copyright Oxide Computer Company */ +import { animated, config, useTransition } from '@react-spring/web' +import cn from 'classnames' import { useState } from 'react' -import { Clipboard16Icon, Success12Icon, useTimeout } from '@oxide/ui' +import { Copy12Icon, Success12Icon, useTimeout } from '@oxide/ui' export const CopyToClipboard = ({ ariaLabel = 'Click to copy this text', @@ -27,18 +29,35 @@ export const CopyToClipboard = ({ }) } + const transitions = useTransition(hasCopied, { + from: { opacity: 0, transform: 'scale(0.8)' }, + enter: { opacity: 1, transform: 'scale(1)' }, + leave: { opacity: 0, transform: 'scale(0.8)' }, + config: config.stiff, + trail: 100, + initial: null, + }) + return ( ) } diff --git a/package-lock.json b/package-lock.json index 24ded399aa..54b05e76a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@floating-ui/react": "^0.26.4", "@headlessui/react": "^1.7.17", - "@oxide/design-system": "^1.2.9", + "@oxide/design-system": "^1.2.10", "@oxide/identicon": "0.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", @@ -2543,9 +2543,9 @@ "dev": true }, "node_modules/@oxide/design-system": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@oxide/design-system/-/design-system-1.2.9.tgz", - "integrity": "sha512-uLKLFEmr7DTYXqTVhI1rdTRHR2x2tkkEGC2o2gzTse6fqvRxoKGAMhlWt2rMJMCI0hxgUIYuYPCaWO6/6smNBA==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@oxide/design-system/-/design-system-1.2.10.tgz", + "integrity": "sha512-L7KhX2rRYy/+QDnc7yW5ie/5IzmX+gCB8R6+zSjt7f+Za9oRkLHecw47KLUWr5lOTgJM4QaJubrlD0nUYb0cJw==", "dependencies": { "@figma-export/output-components-as-svgr": "^4.7.0", "@floating-ui/react": "^0.25.1", @@ -23098,9 +23098,9 @@ "dev": true }, "@oxide/design-system": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@oxide/design-system/-/design-system-1.2.9.tgz", - "integrity": "sha512-uLKLFEmr7DTYXqTVhI1rdTRHR2x2tkkEGC2o2gzTse6fqvRxoKGAMhlWt2rMJMCI0hxgUIYuYPCaWO6/6smNBA==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@oxide/design-system/-/design-system-1.2.10.tgz", + "integrity": "sha512-L7KhX2rRYy/+QDnc7yW5ie/5IzmX+gCB8R6+zSjt7f+Za9oRkLHecw47KLUWr5lOTgJM4QaJubrlD0nUYb0cJw==", "requires": { "@figma-export/output-components-as-svgr": "^4.7.0", "@floating-ui/react": "^0.25.1", diff --git a/package.json b/package.json index e89b7671e7..a3a98d812f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dependencies": { "@floating-ui/react": "^0.26.4", "@headlessui/react": "^1.7.17", - "@oxide/design-system": "^1.2.9", + "@oxide/design-system": "^1.2.10", "@oxide/identicon": "0.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", From 0e16ec2cd58340d4ea8335d8b1965de2b24ea72a Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 16 Jan 2024 12:42:48 -0600 Subject: [PATCH 04/16] basic externalIps table. every instance still has the same IP though --- .../project/instances/instance/InstancePage.tsx | 3 +-- libs/api-mocks/external-ip.ts | 12 ++++++++++++ libs/api-mocks/index.ts | 1 + libs/api-mocks/msw/db.ts | 1 + libs/api-mocks/msw/handlers.ts | 12 ++---------- 5 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 libs/api-mocks/external-ip.ts diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index f36c8c289c..6746045b5c 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -35,7 +35,7 @@ InstancePage.loader = async ({ params }: LoaderFunctionArgs) => { query: { project }, }) await apiQueryClient.prefetchQuery('instanceNetworkInterfaceList', { - query: { project, instance, limit: 10 }, + query: { project, instance }, }) await apiQueryClient.prefetchQuery('instanceExternalIpList', { path: { instance }, @@ -66,7 +66,6 @@ export function InstancePage() { query: { project: instanceSelector.project, instance: instanceSelector.instance, - limit: 10, }, }) diff --git a/libs/api-mocks/external-ip.ts b/libs/api-mocks/external-ip.ts new file mode 100644 index 0000000000..1537f198d8 --- /dev/null +++ b/libs/api-mocks/external-ip.ts @@ -0,0 +1,12 @@ +import type { ExternalIp } from '@oxide/api' + +import type { Json } from './json-type' + +// TODO: this type represents the API response, but we need to mock more +// structure in order to be able to look up IPs for a particular instance +export const externalIps: Json = [ + { + ip: '123.4.56.7', + kind: 'ephemeral', + }, +] diff --git a/libs/api-mocks/index.ts b/libs/api-mocks/index.ts index 7a5b5fc160..6f5db2a9e7 100644 --- a/libs/api-mocks/index.ts +++ b/libs/api-mocks/index.ts @@ -7,6 +7,7 @@ */ export * from './disk' +export * from './external-ip' export * from './image' export * from './instance' export * from './network-interface' diff --git a/libs/api-mocks/msw/db.ts b/libs/api-mocks/msw/db.ts index 4f0babddb3..7047870da7 100644 --- a/libs/api-mocks/msw/db.ts +++ b/libs/api-mocks/msw/db.ts @@ -202,6 +202,7 @@ const initDb = { /** Join table for `users` and `userGroups` */ groupMemberships: [...mock.groupMemberships], images: [...mock.images], + externalIps: [...mock.externalIps], instances: [...mock.instances], networkInterfaces: [mock.networkInterface], physicalDisks: [...mock.physicalDisks], diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index f7e35eaf54..006cdd3d98 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -445,16 +445,8 @@ export const handlers = makeHandlers({ }, instanceExternalIpList({ path, query }) { lookup.instance({ ...path, ...query }) - - // TODO: proper mock table - return { - items: [ - { - ip: '123.4.56.7', - kind: 'ephemeral', - } as const, - ], - } + // endpoint is not paginated. or rather, it's fake paginated + return { items: db.externalIps } }, instanceNetworkInterfaceList({ query }) { const instance = lookup.instance(query) From b20ce6c6c6584c081dc05ee0871e66f36eedb775 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 16 Jan 2024 12:50:41 -0600 Subject: [PATCH 05/16] make external IPs legit --- libs/api-mocks/external-ip.ts | 37 +++++++++++++++++++++++++++++++--- libs/api-mocks/msw/handlers.ts | 7 +++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/libs/api-mocks/external-ip.ts b/libs/api-mocks/external-ip.ts index 1537f198d8..2092e1a95a 100644 --- a/libs/api-mocks/external-ip.ts +++ b/libs/api-mocks/external-ip.ts @@ -1,12 +1,43 @@ import type { ExternalIp } from '@oxide/api' +import { instances } from './instance' import type { Json } from './json-type' +type DbExternalIp = { + instance_id: string + external_ip: Json +} + // TODO: this type represents the API response, but we need to mock more // structure in order to be able to look up IPs for a particular instance -export const externalIps: Json = [ +export const externalIps: DbExternalIp[] = [ + { + instance_id: instances[0].id, + external_ip: { + ip: `123.4.56.0`, + kind: 'ephemeral', + }, + }, + // middle one has no IPs + { + instance_id: instances[2].id, + external_ip: { + ip: `123.4.56.1`, + kind: 'ephemeral', + }, + }, + { + instance_id: instances[2].id, + external_ip: { + ip: `123.4.56.2`, + kind: 'ephemeral', + }, + }, { - ip: '123.4.56.7', - kind: 'ephemeral', + instance_id: instances[2].id, + external_ip: { + ip: `123.4.56.3`, + kind: 'ephemeral', + }, }, ] diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index 006cdd3d98..ec076a34be 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -444,9 +444,12 @@ export const handlers = makeHandlers({ return disk }, instanceExternalIpList({ path, query }) { - lookup.instance({ ...path, ...query }) + const instance = lookup.instance({ ...path, ...query }) + const externalIps = db.externalIps + .filter((eip) => eip.instance_id === instance.id) + .map((eip) => eip.external_ip) // endpoint is not paginated. or rather, it's fake paginated - return { items: db.externalIps } + return { items: externalIps } }, instanceNetworkInterfaceList({ query }) { const instance = lookup.instance(query) From 9779f972eade6de0661f3abf3e27fc68a739339b Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 16 Jan 2024 12:52:38 -0600 Subject: [PATCH 06/16] add file license --- libs/api-mocks/external-ip.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/api-mocks/external-ip.ts b/libs/api-mocks/external-ip.ts index 2092e1a95a..09b2f08fd1 100644 --- a/libs/api-mocks/external-ip.ts +++ b/libs/api-mocks/external-ip.ts @@ -1,3 +1,10 @@ +/* + * 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 type { ExternalIp } from '@oxide/api' import { instances } from './instance' From acceef978b3515e719864a5f0f5ab9f366fd2fef Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 16 Jan 2024 13:15:07 -0600 Subject: [PATCH 07/16] fix fallback logic around no IPs --- .../instances/instance/InstancePage.tsx | 2 +- .../instances/instance/tabs/NetworkingTab.tsx | 37 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index 6746045b5c..fd23b27c93 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -119,7 +119,7 @@ export function InstancePage() { - {primaryVpcId ? VpcNameFromId({ value: primaryVpcId }) : '-'} + {primaryVpcId ? VpcNameFromId({ value: primaryVpcId }) : <>—} diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 62983f15dc..b034abbb68 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -27,6 +27,7 @@ import { Spinner, Success12Icon, } from '@oxide/ui' +import { intersperse } from '@oxide/util' import CreateNetworkInterfaceForm from 'app/forms/network-interface-create' import EditNetworkInterfaceForm from 'app/forms/network-interface-edit' @@ -79,6 +80,19 @@ const SubnetNameFromId = ({ value }: { value: string }) => { return {subnet.name} } +function IpLink({ ip }: { ip: string }) { + return ( + + {ip} + + ) +} + export function ExternalIpsFromInstanceName({ value: primary }: { value: boolean }) { const { project, instance } = useInstanceSelector() const { data, isLoading } = useApiQuery('instanceExternalIpList', { @@ -88,23 +102,18 @@ export function ExternalIpsFromInstanceName({ value: primary }: { value: boolean if (isLoading) return
// loading - const ips = data?.items.map((eip, index, array) => ( - - - {eip.ip} - - {index < array.length - 1 && / } - - )) + const ips = data?.items + ? intersperse( + data.items.map((eip) => ), + / + ) + : undefined return (
- {primary ? ips : <>—} + {/* primary is about the networking tab call site, ips.length check is + * about the table at the top of the instance page */} + {primary && ips && ips.length > 0 ? ips : <>—} {/* If there's exactly one IP here, render a copy to clipboard button */} {data?.items.length === 1 && }
From 4fa26fdd1dc8fc090d3626991c9613d77d642d03 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 16 Jan 2024 14:07:54 -0600 Subject: [PATCH 08/16] skeleton cell component --- .../project/instances/instance/tabs/NetworkingTab.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index b034abbb68..fdcedc5fcd 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -27,7 +27,7 @@ import { Spinner, Success12Icon, } from '@oxide/ui' -import { intersperse } from '@oxide/util' +import { classed, intersperse } from '@oxide/util' import CreateNetworkInterfaceForm from 'app/forms/network-interface-create' import EditNetworkInterfaceForm from 'app/forms/network-interface-edit' @@ -42,6 +42,8 @@ import { pb } from 'app/util/path-builder' import { fancifyStates } from './common' +export const Skeleton = classed.div`h-4 w-12 rounded bg-tertiary motion-safe:animate-pulse` + export const VpcNameFromId = ({ value }: { value: string }) => { const projectSelector = useProjectSelector() const { data: vpc, isError } = useApiQuery( @@ -54,8 +56,7 @@ export const VpcNameFromId = ({ value }: { value: string }) => { // possible because you can't delete a VPC that has child resources, but let's // be safe if (isError) return Deleted - if (!vpc) - return
// loading + if (!vpc) return return ( // loading + if (isLoading) return const ips = data?.items ? intersperse( From 9a74c9246d9a0c62253040a37a33ae663836f827 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 17 Jan 2024 09:37:23 +0000 Subject: [PATCH 09/16] Give properties row a fixed height --- libs/ui/lib/properties-table/PropertiesTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/ui/lib/properties-table/PropertiesTable.tsx b/libs/ui/lib/properties-table/PropertiesTable.tsx index 26233b4b53..6154e7a883 100644 --- a/libs/ui/lib/properties-table/PropertiesTable.tsx +++ b/libs/ui/lib/properties-table/PropertiesTable.tsx @@ -28,7 +28,7 @@ export function PropertiesTable({ className, children }: PropertiesTableProps) {
{children} @@ -45,7 +45,7 @@ PropertiesTable.Row = ({ label, children }: PropertiesTableRowProps) => ( {label} -
+
{children}
From c8e2cc829af9926fd3981b4b1c1b55f890c13be3 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 17 Jan 2024 09:43:17 +0000 Subject: [PATCH 10/16] `EmptyCellContent` component instead of `—` directly --- app/pages/project/instances/instance/InstancePage.tsx | 8 ++++++-- .../project/instances/instance/tabs/NetworkingTab.tsx | 6 +++++- app/pages/system/SiloPage.tsx | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index fd23b27c93..9289c25d2a 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -26,7 +26,11 @@ import { getInstanceSelector, useInstanceSelector, useQuickActions } from 'app/h import { pb } from 'app/util/path-builder' import { useMakeInstanceActions } from '../actions' -import { ExternalIpsFromInstanceName, VpcNameFromId } from './tabs/NetworkingTab' +import { + EmptyCellContent, + ExternalIpsFromInstanceName, + VpcNameFromId, +} from './tabs/NetworkingTab' InstancePage.loader = async ({ params }: LoaderFunctionArgs) => { const { project, instance } = getInstanceSelector(params) @@ -119,7 +123,7 @@ export function InstancePage() { - {primaryVpcId ? VpcNameFromId({ value: primaryVpcId }) : <>—} + {primaryVpcId ? VpcNameFromId({ value: primaryVpcId }) : } diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index fdcedc5fcd..4c487dabec 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -44,6 +44,10 @@ import { fancifyStates } from './common' export const Skeleton = classed.div`h-4 w-12 rounded bg-tertiary motion-safe:animate-pulse` +export const EmptyCellContent = () => ( + +) + export const VpcNameFromId = ({ value }: { value: string }) => { const projectSelector = useProjectSelector() const { data: vpc, isError } = useApiQuery( @@ -113,7 +117,7 @@ export function ExternalIpsFromInstanceName({ value: primary }: { value: boolean
{/* primary is about the networking tab call site, ips.length check is * about the table at the top of the instance page */} - {primary && ips && ips.length > 0 ? ips : <>—} + {primary && ips && ips.length > 0 ? ips : } {/* If there's exactly one IP here, render a copy to clipboard button */} {data?.items.length === 1 && }
diff --git a/app/pages/system/SiloPage.tsx b/app/pages/system/SiloPage.tsx index 12f4c63d08..ff98ee1a5d 100644 --- a/app/pages/system/SiloPage.tsx +++ b/app/pages/system/SiloPage.tsx @@ -27,6 +27,8 @@ import { import { getSiloSelector, useSiloSelector } from 'app/hooks' import { pb } from 'app/util/path-builder' +import { EmptyCellContent } from '../project/instances/instance/tabs/NetworkingTab' + const EmptyState = () => ( } title="No identity providers" /> ) @@ -73,7 +75,7 @@ export function SiloPage() { Fleet role mapping {roleMapPairs.length === 0 ? ( -

+ ) : (