Skip to content

Commit

Permalink
Add SSH info to Instance Connect tab (#2339)
Browse files Browse the repository at this point in the history
* Add box to Connect page with SSH info

* Remove CopyToClipboard; refactor

* Copy tweak

* Update size; remove unnecessary copy

* tweak for more cardiness

* fix ladle type error

* Placeholder copy update; still in flux

* add correct link to new remote access guide

* extract InlineCode, tweak styling, marginally gooder copy

---------

Co-authored-by: David Crespo <[email protected]>
  • Loading branch information
charliepark and david-crespo authored Aug 22, 2024
1 parent 8dcddce commit 1bb9270
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 55 deletions.
22 changes: 22 additions & 0 deletions app/components/InstanceDocsPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { Instances16Icon } from '@oxide/design-system/icons/react'

import { docLinks } from '~/util/links'

import { DocsPopover } from './DocsPopover'

export const InstanceDocsPopover = () => (
<DocsPopover
heading="instances"
icon={<Instances16Icon />}
summary="Instances are virtual machines that run on the Oxide platform."
links={[docLinks.instances, docLinks.remoteAccess, docLinks.instanceActions]}
/>
)
12 changes: 3 additions & 9 deletions app/pages/project/instances/InstancesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { useMemo } from 'react'
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

import { apiQueryClient, usePrefetchedApiQuery, type Instance } from '@oxide/api'
import { Instances16Icon, Instances24Icon } from '@oxide/design-system/icons/react'
import { Instances24Icon } from '@oxide/design-system/icons/react'

import { DocsPopover } from '~/components/DocsPopover'
import { InstanceDocsPopover } from '~/components/InstanceDocsPopover'
import { RefreshButton } from '~/components/RefreshButton'
import { getProjectSelector, useProjectSelector, useQuickActions } from '~/hooks'
import { InstanceStatusCell } from '~/table/cells/InstanceStatusCell'
Expand All @@ -25,7 +25,6 @@ import { CreateLink } from '~/ui/lib/CreateButton'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'

import { useMakeInstanceActions } from './actions'
Expand Down Expand Up @@ -131,12 +130,7 @@ export function InstancesPage() {
<>
<PageHeader>
<PageTitle icon={<Instances24Icon />}>Instances</PageTitle>
<DocsPopover
heading="instances"
icon={<Instances16Icon />}
summary="Instances are virtual machines that run on the Oxide platform."
links={[docLinks.instances, docLinks.instanceActions]}
/>
<InstanceDocsPopover />
</PageHeader>
<TableActions>
<RefreshButton onClick={refetchInstances} />
Expand Down
12 changes: 3 additions & 9 deletions app/pages/project/instances/instance/InstancePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import {
usePrefetchedApiQuery,
type InstanceNetworkInterface,
} from '@oxide/api'
import { Instances16Icon, Instances24Icon } from '@oxide/design-system/icons/react'
import { Instances24Icon } from '@oxide/design-system/icons/react'

import { instanceTransitioning } from '~/api/util'
import { DocsPopover } from '~/components/DocsPopover'
import { ExternalIps } from '~/components/ExternalIps'
import { InstanceDocsPopover } from '~/components/InstanceDocsPopover'
import { MoreActionsMenu } from '~/components/MoreActionsMenu'
import { RefreshButton } from '~/components/RefreshButton'
import { RouteTabs, Tab } from '~/components/RouteTabs'
Expand All @@ -32,7 +32,6 @@ import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { Spinner } from '~/ui/lib/Spinner'
import { Tooltip } from '~/ui/lib/Tooltip'
import { Truncate } from '~/ui/lib/Truncate'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'

import { useMakeInstanceActions } from '../actions'
Expand Down Expand Up @@ -151,12 +150,7 @@ export function InstancePage() {
<PageHeader>
<PageTitle icon={<Instances24Icon />}>{instance.name}</PageTitle>
<div className="inline-flex gap-2">
<DocsPopover
heading="instances"
icon={<Instances16Icon />}
summary="Instances are virtual machines that run on the Oxide platform."
links={[docLinks.instances, docLinks.instanceActions]}
/>
<InstanceDocsPopover />
<RefreshButton onClick={refreshData} />
<MoreActionsMenu label="Instance actions" actions={actions} />
</div>
Expand Down
89 changes: 71 additions & 18 deletions app/pages/project/instances/instance/tabs/ConnectTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,86 @@
* Copyright Oxide Computer Company
*/

import { Link } from 'react-router-dom'
import { Link, type LoaderFunctionArgs } from 'react-router-dom'

import { apiQueryClient, usePrefetchedApiQuery } from '~/api'
import { EquivalentCliCommand } from '~/components/EquivalentCliCommand'
import { useInstanceSelector } from '~/hooks'
import { getInstanceSelector, useInstanceSelector } from '~/hooks'
import { buttonStyle } from '~/ui/lib/Button'
import { SettingsGroup } from '~/ui/lib/SettingsGroup'
import { InlineCode } from '~/ui/lib/InlineCode'
import { LearnMore, SettingsGroup } from '~/ui/lib/SettingsGroup'
import { cliCmd } from '~/util/cli-cmd'
import { links } from '~/util/links'
import { pb } from '~/util/path-builder'

ConnectTab.loader = async ({ params }: LoaderFunctionArgs) => {
const { project, instance } = getInstanceSelector(params)
await apiQueryClient.prefetchQuery('instanceExternalIpList', {
path: { instance },
query: { project },
})
return null
}

export function ConnectTab() {
const { project, instance } = useInstanceSelector()
const { data: externalIps } = usePrefetchedApiQuery('instanceExternalIpList', {
path: { instance },
query: { project },
})
const floatingIp = externalIps.items.find((ip) => ip.kind === 'floating')
const ephemeralIp = externalIps.items.find((ip) => ip.kind === 'ephemeral')
// prefer floating, fall back to ephemeral
const externalIp = floatingIp?.ip || ephemeralIp?.ip

return (
<SettingsGroup.Container>
<SettingsGroup.Body>
<SettingsGroup.Title>Serial console</SettingsGroup.Title>
Connect to your instance&rsquo;s serial console
</SettingsGroup.Body>
<SettingsGroup.Footer>
<EquivalentCliCommand command={cliCmd.serialConsole({ project, instance })} />
<Link
to={pb.serialConsole({ project, instance })}
className={buttonStyle({ size: 'sm' })}
>
Connect
</Link>
</SettingsGroup.Footer>
</SettingsGroup.Container>
<div className="space-y-6">
<SettingsGroup.Container>
<SettingsGroup.Body>
<SettingsGroup.Title>Serial console</SettingsGroup.Title>
Connect to your instance&rsquo;s serial console
</SettingsGroup.Body>
<SettingsGroup.Footer>
<div>
<LearnMore text="Serial Console" href={links.serialConsoleDocs} />
</div>
<div className="flex gap-3">
<EquivalentCliCommand command={cliCmd.serialConsole({ project, instance })} />
<Link
to={pb.serialConsole({ project, instance })}
className={buttonStyle({ size: 'sm' })}
>
Connect
</Link>
</div>
</SettingsGroup.Footer>
</SettingsGroup.Container>
<SettingsGroup.Container>
<SettingsGroup.Body>
<SettingsGroup.Title>SSH</SettingsGroup.Title>
<p>
If your instance allows SSH access, connect with{' '}
<InlineCode>ssh [username]@{externalIp || '[external IP]'}</InlineCode>.
</p>
{!externalIp && (
<p className="mt-2">
This instance has no external IP address. You can add one on the{' '}
<Link
className="link-with-underline"
to={pb.instanceNetworking({ project, instance })}
>
networking
</Link>{' '}
tab.
</p>
)}
</SettingsGroup.Body>
<SettingsGroup.Footer>
<div>
<LearnMore text="SSH" href={links.sshDocs} />
</div>
</SettingsGroup.Footer>
</SettingsGroup.Container>
</div>
)
}
1 change: 1 addition & 0 deletions app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export const routes = createRoutesFromElements(
<Route
path="connect"
element={<ConnectTab />}
loader={ConnectTab.loader}
handle={{ crumb: 'Connect' }}
/>
</Route>
Expand Down
11 changes: 11 additions & 0 deletions app/ui/lib/InlineCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { classed } from '~/util/classed'

export const InlineCode = classed.code`whitespace-nowrap rounded-sm px-[3px] py-[1px] text-mono-sm !normal-case bg-raise border border-secondary mx-px`
7 changes: 3 additions & 4 deletions app/ui/lib/SettingsGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@
import { Link } from 'react-router-dom'

import { Button, buttonStyle } from './Button'
import { SettingsGroup } from './SettingsGroup'
import { LearnMore, SettingsGroup } from './SettingsGroup'

export const Default = () => (
<SettingsGroup.Container>
<SettingsGroup.Body>
<SettingsGroup.Title>Serial console</SettingsGroup.Title>
Connect to your instance&rsquo;s serial console
</SettingsGroup.Body>
<SettingsGroup.Footer
docsLink={{ text: 'math', href: 'https://en.wikipedia.org/wiki/Mathematics' }}
>
<SettingsGroup.Footer>
<LearnMore text="math" href="https://en.wikipedia.org/wiki/Mathematics" />
<Link to="/" className={buttonStyle({ size: 'sm' })}>
Connect
</Link>
Expand Down
23 changes: 8 additions & 15 deletions app/ui/lib/SettingsGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,25 @@ import { OpenLink12Icon } from '@oxide/design-system/icons/react'

import { classed } from '~/util/classed'

const LearnMore = ({ href, text }: { href: string; text: React.ReactNode }) => (
export const LearnMore = ({ href, text }: { href: string; text: React.ReactNode }) => (
<>
Learn more about{' '}
<a href={href} className="text-accent-secondary hover:text-accent">
<a
href={href}
className="inline-flex items-center text-accent-secondary hover:text-accent"
target="_blank"
rel="noreferrer"
>
{text}
<OpenLink12Icon className="ml-1 align-middle" />
</a>
</>
)

type FooterProps = {
/** Link text */
children: React.ReactNode
docsLink?: { text: string; href: string }
}

/** Use size=sm on buttons and links! */
export const SettingsGroup = {
Container: classed.div`w-full max-w-[660px] rounded-lg border text-sans-md text-secondary border-default`,
Body: classed.div`p-6`,
Title: classed.div`mb-1 text-sans-lg text-default`,
Footer: ({ children, docsLink }: FooterProps) => (
<div className="flex items-center justify-between border-t px-6 py-3 border-default">
{/* div always present to keep the buttons right-aligned */}
<div className="text-tertiary">{docsLink && <LearnMore {...docsLink} />}</div>
<div className="flex gap-3">{children}</div>
</div>
),
Footer: classed.div`flex items-center justify-between border-t px-6 py-3 border-default h-14`,
}
1 change: 1 addition & 0 deletions app/util/classed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const make =

export const classed = {
button: make('button'),
code: make('code'),
div: make('div'),
footer: make('footer'),
h1: make('h1'),
Expand Down
9 changes: 9 additions & 0 deletions app/util/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*
* Copyright Oxide Computer Company
*/

const remoteAccess = 'https://docs.oxide.computer/guides/remote-access'

export const links = {
accessDocs: 'https://docs.oxide.computer/guides/configuring-access',
cloudInitFormat: 'https://cloudinit.readthedocs.io/en/latest/explanation/format.html',
Expand All @@ -31,6 +34,8 @@ export const links = {
'https://docs.oxide.computer/guides/architecture/service-processors#_server_sled',
snapshotsDocs:
'https://docs.oxide.computer/guides/managing-disks-and-snapshots#_snapshots',
serialConsoleDocs: remoteAccess + '#serial-console',
sshDocs: remoteAccess + '#ssh',
sshKeysDocs: 'https://docs.oxide.computer/guides/user-settings#_ssh_keys',
storageDocs:
'https://docs.oxide.computer/guides/architecture/os-hypervisor-storage#_storage',
Expand Down Expand Up @@ -83,6 +88,10 @@ export const docLinks = {
href: links.quickStart,
linkText: 'Quick Start',
},
remoteAccess: {
href: remoteAccess,
linkText: 'Remote Access',
},
routers: {
href: links.routersDocs,
linkText: 'Custom Routers',
Expand Down

0 comments on commit 1bb9270

Please sign in to comment.