8000 feat: UI/UX for regions by Emyrk · Pull Request #7283 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: UI/UX for regions #7283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
26d3497
chore: Allow regular users to query for all workspaces
Emyrk Apr 24, 2023
3203ad7
Begin work on FE to add workspace proxy options to account settings
Emyrk Apr 24, 2023
a9ad485
Take origin file
Emyrk Apr 25, 2023
bde8870
Remove excess diffs
Emyrk Apr 25, 2023
69ce5f0
fixup! Remove excess diffs
Emyrk Apr 25, 2023
1daa32f
Update proxy page for regions endpoint
Emyrk Apr 25, 2023
b2e3efb
Some basic selector for proxies
Emyrk Apr 25, 2023
f78935f
Make hook for preferred proxy
Emyrk Apr 25, 2023
7a2e78e
Make fmt
Emyrk Apr 25, 2023
9b598e8
Typo
Emyrk Apr 25, 2023
17f9b00
Create workspace proxy context
Emyrk Apr 26, 2023
0683412
Use new context
Emyrk Apr 26, 2023
69c5734
fixup! Use new context
Emyrk Apr 26, 2023
9879476
WorkspaceProxy context syncs with coderd on region responses
Emyrk Apr 26, 2023
7d163fd
Make fmt
Emyrk Apr 26, 2023
e400810
Move dashboard provider
Emyrk Apr 26, 2023
02bcb84
Fix authenticated providers
Emyrk Apr 26, 2023
f9446c2
Fix authenticated renders
Emyrk Apr 26, 2023
9281333
Merge remote-tracking branch 'origin/main' into stevenmasley/regions
Emyrk Apr 26, 2023
8cc227f
Make fmt
Emyrk Apr 26, 2023
63dc985
Use auth render
Emyrk Apr 26, 2023
f4b6921
Fix terminal test render
Emyrk Apr 26, 2023
322fda6
Make fmt
Emyrk Apr 26, 2023
89efc57
Fix local storage load
Emyrk Apr 27, 2023
48a0beb
Fix terminals on the frontend to use proxies
Emyrk Apr 27, 2023
77d943f
Remove CSP hole
Emyrk Apr 27, 2023
75b8fd4
Add comment on origin patterns
Emyrk Apr 27, 2023
3391e84
Add unit test for getURLs
Emyrk Apr 27, 2023
4075b92
remove some TODOs
Emyrk Apr 27, 2023
b79b460
Add another store
Emyrk Apr 27, 2023
e879160
Update site/src/components/TerminalLink/TerminalLink.tsx
Emyrk Apr 27, 2023
27ef4a9
Fix stories
Emyrk Apr 27, 2023
eb38e95
Move providers into requrie auth
Emyrk Apr 27, 2023
1f8cae4
Fix imports
Emyrk Apr 27, 2023
6a22181
Fix 2 stories
Emyrk Apr 27, 2023
51bdaa2
Stories did not have subdomains on
Emyrk Apr 27, 2023
909801c
Merge remote-tracking branch 'origin/main' into stevenmasley/regions
Emyrk Apr 27, 2023
836c5a4
Fmt after merge
Emyrk Apr 27, 2023
0d0ed87
Fix port forward story
Emyrk Apr 27, 2023
6ed5fe4
ProxyPageView -> ProxyView
Emyrk Apr 27, 2023
163bbff
PR comment cleanup
Emyrk Apr 27, 2023
7dee309
Fix moon feature flag panic
Emyrk Apr 27, 2023
b7cfb39
Make fmt
Emyrk Apr 27, 2023
6b19118
Fix typo
Emyrk Apr 27, 2023
3fed785
Rename getUrls
Emyrk Apr 28, 2023
5bb44e8
Rename regions to proxies
Emyrk Apr 28, 2023
c868fc9
Only do 1 api call based on experiment
Emyrk Apr 28, 2023
edbe6e4
Cleanup args to take just the selected proxy
Emyrk Apr 28, 2023
eb6493c
Renames regions -> proxies
Emyrk Apr 28, 2023
017b3db
Fix stories
Emyrk Apr 28, 2023
c59a6ba
Move funciton back to bottom
Emyrk Apr 28, 2023
87e0b6d
Fix onSuccess of proxy provider
Emyrk Apr 28, 2023
fef5b00
Make fmt
Emyrk Apr 28, 2023
d33371d
Simplify some ts
Emyrk Apr 28, 2023
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
Prev Previous commit
Next Next commit
WorkspaceProxy context syncs with coderd on region responses
  • Loading branch information
Emyrk committed Apr 26, 2023
commit 9879476f04c61fb21b263dfd99381776e2a0aef7
21 changes: 8 additions & 13 deletions site/src/components/AppLink/AppLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,27 @@ import * as TypesGen from "../../api/typesGenerated"
import { generateRandomString } from "../../utils/random"
import { BaseIcon } from "./BaseIcon"
import { ShareIcon } from "./ShareIcon"
import { usePreferredProxy } from "hooks/usePreferredProxy"
import { useProxy } from "contexts/ProxyContext"

const Language = {
appTitle: (appName: string, identifier: string): string =>
`${appName} - ${identifier}`,
}

export interface AppLinkProps {
appsHost?: string
workspace: TypesGen.Workspace
app: TypesGen.WorkspaceApp
agent: TypesGen.WorkspaceAgent
}

export const AppLink: FC<AppLinkProps> = ({
appsHost,
app,
workspace,
agent,
}) => {
const preferredProxy = usePreferredProxy()
const preferredPathBase = preferredProxy ? preferredProxy.path_app_url : ""
// Use the proxy host subdomain if it's configured.
appsHost = preferredProxy ? preferredProxy.wildcard_hostname : appsHost
const { proxy } = useProxy()
const preferredPathBase = proxy.preferredPathAppURL
const appsHost = proxy.preferredWildcardHostname

const styles = useStyles()
const username = workspace.owner_name
Expand All @@ -49,13 +46,11 @@ export const AppLink: FC<AppLinkProps> = ({

// The backend redirects if the trailing slash isn't included, so we add it
// here to avoid extra roundtrips.
let href = `${preferredPathBase}/@${username}/${workspace.name}.${
agent.name
}/apps/${encodeURIComponent(appSlug)}/`
let href = `${preferredPathBase}/@${username}/${workspace.name}.${agent.name
}/apps/${encodeURIComponent(appSlug)}/`
if (app.command) {
href = `${preferredPathBase}/@${username}/${workspace.name}.${
agent.name
}/terminal?command=${encodeURIComponent(app.command)}`
href = `${preferredPathBase}/@${username}/${workspace.name}.${agent.name
}/terminal?command=${encodeURIComponent(app.command)}`
}

// TODO: @emyrk handle proxy subdomains.
Expand Down
11 changes: 4 additions & 7 deletions site/src/components/PortForwardButton/PortForwardButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,18 @@ export const portForwardURL = (
): string => {
const { location } = window

const subdomain = `${
isNaN(port) ? 3000 : port
}--${agentName}--${workspaceName}--${username}`
const subdomain = `${isNaN(port) ? 3000 : port
}--${agentName}--${workspaceName}--${username}`
return `${location.protocol}//${host}`.replace("*", subdomain)
}

const TooltipView: React.FC<PortForwardButtonProps> = (props) => {
const { host, workspaceName, agentName, agentId, username } = props
const preferredProxy = usePreferredProxy()
const portHost = preferredProxy ? preferredProxy.wildcard_hostname : host

const styles = useStyles()
const [port, setPort] = useState("3000")
const urlExample = portForwardURL(
portHost,
host,
parseInt(port),
agentName,
workspaceName,
Expand Down Expand Up @@ -107,7 +104,7 @@ const TooltipView: React.FC<PortForwardButtonProps> = (props) => {
{ports &&
ports.map((p, i) => {
const url = portForwardURL(
portHost,
host,
p.port,
agentName,
workspaceName,
Expand Down
4 changes: 1 addition & 3 deletions site/src/components/Resources/AgentRow.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ Example.args = {
'set -eux -o pipefail\n\n# install and start code-server\ncurl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.8.3\n/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &\n\n\nif [ ! -d ~/coder ]; then\n mkdir -p ~/coder\n\n git clone https://github.com/coder/coder ~/coder\nfi\n\nsudo service docker start\nDOTFILES_URI=" "\nrm -f ~/.personalize.log\nif [ -n "${DOTFILES_URI// }" ]; then\n coder dotfiles "$DOTFILES_URI" -y 2>&1 | tee -a ~/.personalize.log\nfi\nif [ -x ~/personalize ]; then\n ~/personalize 2>&1 | tee -a ~/.personalize.log\nelif [ -f ~/personalize ]; then\n echo "~/personalize is not executable, skipping..." | tee -a ~/.personalize.log\nfi\n',
},
workspace: MockWorkspace,
applicationsHost: "",
showApps: true,
storybookAgentMetadata: defaultAgentMetadata,
}
Expand Down Expand Up @@ -149,7 +148,6 @@ BunchOfApps.args = {
],
},
workspace: MockWorkspace,
applicationsHost: "",
showApps: true,
}

Expand Down Expand Up @@ -226,7 +224,7 @@ Off.args = {
export const ShowingPortForward = Template.bind({})
ShowingPortForward.args = {
...Example.args,
applicationsHost: "https://coder.com",
// TODO: @emyrk fix this from the proxy context
}

export const Outdated = Template.bind({})
Expand Down
9 changes: 4 additions & 5 deletions site/src/components/Resources/AgentRow.tsx
F438
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ import { AgentMetadata } from "./AgentMetadata"
import { AgentVersion } from "./AgentVersion"
import { AgentStatus } from "./AgentStatus"
import Collapse from "@material-ui/core/Collapse"
import { useProxy } from "contexts/ProxyContext"

export interface AgentRowProps {
agent: WorkspaceAgent
workspace: Workspace
applicationsHost: string | undefined
showApps: boolean
hideSSHButton?: boolean
sshPrefix?: string
Expand All @@ -61,7 +61,6 @@ export interface AgentRowProps {
export const AgentRow: FC<AgentRowProps> = ({
agent,
workspace,
applicationsHost,
showApps,
hideSSHButton,
hideVSCodeDesktopButton,
Expand Down Expand Up @@ -96,6 +95,7 @@ export const AgentRow: FC<AgentRowProps> = ({
const hasStartupFeatures =
Boolean(agent.startup_logs_length) ||
Boolean(logsMachine.context.startupLogs?.length)
const { proxy } = useProxy()

const [showStartupLogs, setShowStartupLogs] = useState(
agent.lifecycle_state !== "ready" && hasStartupFeatures,
Expand Down Expand Up @@ -228,7 +228,6 @@ export const AgentRow: FC<AgentRowProps> = ({
{agent.apps.map((app) => (
<AppLink
key={app.slug}
appsHost={applicationsHost}
app={app}
agent={agent}
workspace={workspace}
Expand All @@ -249,9 +248,9 @@ export const AgentRow: FC<AgentRowProps> = ({
sshPrefix={sshPrefix}
/>
)}
{applicationsHost !== undefined && applicationsHost !== "" && (
{proxy.preferredWildcardHostname !== undefined && proxy.preferredWildcardHostname !== "" && (
<PortForwardButton
host={applicationsHost}
host={proxy.preferredWildcardHostname}
workspaceName={workspace.name}
agentId={agent.id}
agentName={agent.name}
Expand Down
10 changes: 4 additions & 6 deletions site/src/components/TerminalLink/TerminalLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SecondaryAgentButton } from "components/Resources/AgentButton"
import { FC } from "react"
import * as TypesGen from "../../api/typesGenerated"
import { generateRandomString } from "../../utils/random"
import { usePreferredProxy } from "hooks/usePreferredProxy"
import { useProxy } from "contexts/ProxyContext"

export const Language = {
linkText: "Terminal",
Expand All @@ -28,12 +28,10 @@ export const TerminalLink: FC<React.PropsWithChildren<TerminalLinkProps>> = ({
userName = "me",
workspaceName,
}) => {
const preferredProxy = usePreferredProxy()
const preferredPathBase = preferredProxy ? preferredProxy.path_app_url : ""
const { proxy } = useProxy()

const href = `${preferredPathBase}/@${userName}/${workspaceName}${
agentName ? `.${agentName}` : ""
}/terminal`
const href = `${proxy.preferredPathAppURL}/@${userName}/${workspaceName}${agentName ? `.${agentName}` : ""
}/terminal`

return (
<Link
Expand Down
3 changes: 0 additions & 3 deletions site/src/components/Workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export interface WorkspaceProps {
hideVSCodeDesktopButton?: boolean
workspaceErrors: Partial<Record<WorkspaceErrors, Error | unknown>>
buildInfo?: TypesGen.BuildInfoResponse
applicationsHost?: string
sshPrefix?: string
template?: TypesGen.Template
quota_budget?: number
Expand Down Expand Up @@ -88,7 +87,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
hideSSHButton,
hideVSCodeDesktopButton,
buildInfo,
applicationsHost,
sshPrefix,
template,
quota_budget,
Expand Down Expand Up @@ -240,7 +238,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
key={agent.id}
agent={agent}
workspace={workspace}
applicationsHost={applicationsHost}
sshPrefix={sshPrefix}
showApps={canUpdateWorkspace}
hideSSHButton={hideSSHButton}
Expand Down
84 changes: 55 additions & 29 deletions site/src/contexts/ProxyContext.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { useQuery } from "@tanstack/react-query"
import { getApplicationsHost, getWorkspaceProxies } from "api/api"
import { Region } from "api/typesGenerated"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import { createContext, FC, PropsWithChildren, useContext, useState } from "react"

interface ProxyContextValue {
value: PreferredProxy
setValue: (regions: Region[], selectedRegion: Region | undefined) => void
proxy: PreferredProxy
isLoading: boolean
error?: Error | unknown
setProxy: (regions: Region[], selectedRegion: Region | undefined) => void
}

interface PreferredProxy {
// Regions is a list of all the regions returned by coderd.
regions: Region[]
// SelectedRegion is the region the user has selected.
// Do not use the fields 'path_app_url' or 'wildcard_hostname' from this
// object. Use the preferred fields.
selectedRegion: Region | undefined
// PreferredPathAppURL is the URL of the proxy or it is the empty string
// to indicte using relative paths. To add a path to this:
// PreferredPathAppURL + "/path/to/app"
preferredPathAppURL: string
// PreferredWildcardHostname is a hostname that includes a wildcard.
// TODO: If the preferred proxy does not have this set, should we default to'
// the primary's?
// Example: "*.example.com"
preferredWildcardHostname: string
}

Expand All @@ -32,24 +33,56 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
// Try to load the preferred proxy from local storage.
let savedProxy = loadPreferredProxy()
if (!savedProxy) {
savedProxy = getURLs([], undefined)
savedProxy = getURLs([])
}

// The initial state is no regions and no selected region.
const [state, setState] = useState<PreferredProxy>(savedProxy)
const [proxy, setProxy] = useState<PreferredProxy>(savedProxy)
const setAndSaveProxy = (regions: Region[], selectedRegion: Region | undefined) => {
const preferred = getURLs(regions, selectedRegion)
// Save to local storage to persist the user's preference across reloads
// and other tabs.
savePreferredProxy(preferred)
// Set the state for the current context.
setProxy(preferred)
}

const queryKey = ["get-regions"]
const { error: regionsError, isLoading: regionsLoading } = useQuery({
queryKey,
queryFn: getWorkspaceProxies,
// This onSucccess ensures the local storage is synchronized with the
// regions returned by coderd. If the selected region is not in the list,
// then the user selection is removed.
onSuccess: (data) => {
setAndSaveProxy(data.regions, proxy.selectedRegion)
},
})

// ******************************* //
// ** This code can be removed **
// ** when the experimental is **
// ** dropped ** //
const dashboard = useDashboard()
const appHostQueryKey = ["get-application-host"]
const { data: applicationHostResult, error: appHostError, isLoading: appHostLoading } = useQuery({
queryKey: appHostQueryKey,
queryFn: getApplicationsHost,
})
// If the experiment is disabled, then make the setState do a noop.
// This preserves an empty state, which is the default behavior.
if (!dashboard?.experiments.includes("moons")) {
const value = getURLs([])

return (
<ProxyContext.Provider value={{
value: getURLs([], undefined),
setValue: () => {
proxy: {
...value,
preferredWildcardHostname: applicationHostResult?.host || value.preferredWildcardHostname,
},
isLoading: appHostLoading,
error: appHostError,
setProxy: () => {
// Does a noop
},
}}>
Expand All @@ -64,17 +97,12 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {

return (
<ProxyContext.Provider value={{
value: state,
proxy: proxy,
isLoading: regionsLoading,
error: regionsError,
// A function that takes the new regions and selected region and updates
// the state with the appropriate urls.
setValue: (regions, selectedRegion) => {
const preferred = getURLs(regions, selectedRegion)
// Save to local storage to persist the user's preference across reloads
// and other tabs.
savePreferredProxy(preferred)
// Set the state for the current context.
setState(preferred)
},
setProxy: setAndSaveProxy,
}}>
{children}
</ProxyContext.Provider >
Expand All @@ -99,19 +127,19 @@ export const useProxy = (): ProxyContextValue => {
* @param regions Is the list of regions returned by coderd. If this is empty, default behavior is used.
* @param selectedRegion Is the region the user has selected. If this is undefined, default behavior is used.
*/
const getURLs = (regions: Region[], selectedRegion: Region | undefined): PreferredProxy => {
const getURLs = (regions: Region[], selectedRegion?: Region): PreferredProxy => {
// By default we set the path app to relative and disable wilcard hostnames.
// We will set these values if we find a proxy we can use that supports them.
let pathAppURL = ""
let wildcardHostname = ""

if (selectedRegion === undefined) {
// If a region is selected, make sure it is in the list of regions. If it is not
// we should default to the primary.
selectedRegion = regions.find((region) => selectedRegion && region.id === selectedRegion.id)

if (!selectedRegion) {
// If no region is selected, default to the primary region.
selectedRegion = regions.find((region) => region.name === "primary")
} else {
// If a region is selected, make sure it is in the list of regions. If it is not
// we should default to the primary.
selectedRegion = regions.find((region) => region.id === selectedRegion?.id)
}

// Only use healthy regions.
Expand All @@ -126,10 +154,8 @@ const getURLs = (regions: Region[], selectedRegion: Region | undefined): Preferr

// TODO: @emyrk Should we notify the user if they had an unhealthy region selected?


return {
regions: regions,
selectedRegion: selectedRegion,
selectedRegion,
// Trim trailing slashes to be consistent
preferredPathAppURL: pathAppURL.replace(/\/$/, ""),
preferredWildcardHostname: wildcardHostname,
Expand Down
Loading
0