mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: use app wildcards for apps if configured (#4263)
* feat: use app wildcards for apps if configured
* feat: relative_path -> subdomain
- rename relative_path -> subdomain when referring to apps
- migrate workspace_apps.relative_path to workspace_apps.subdomain
- upgrade coder/coder terraform module to 0.5.0
This commit is contained in:
@@ -603,6 +603,7 @@ export interface WorkspaceApp {
|
||||
readonly name: string
|
||||
readonly command?: string
|
||||
readonly icon?: string
|
||||
readonly subdomain: boolean
|
||||
readonly healthcheck: Healthcheck
|
||||
readonly health: WorkspaceAppHealth
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const Template: Story<AppLinkProps> = (args) => <AppLink {...args} />
|
||||
|
||||
export const WithIcon = Template.bind({})
|
||||
WithIcon.args = {
|
||||
userName: "developer",
|
||||
username: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
appIcon: "/icon/code.svg",
|
||||
@@ -20,7 +20,7 @@ WithIcon.args = {
|
||||
|
||||
export const WithoutIcon = Template.bind({})
|
||||
WithoutIcon.args = {
|
||||
userName: "developer",
|
||||
username: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "healthy",
|
||||
@@ -28,7 +28,7 @@ WithoutIcon.args = {
|
||||
|
||||
export const HealthDisabled = Template.bind({})
|
||||
HealthDisabled.args = {
|
||||
userName: "developer",
|
||||
username: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "disabled",
|
||||
@@ -36,7 +36,7 @@ HealthDisabled.args = {
|
||||
|
||||
export const HealthInitializing = Template.bind({})
|
||||
HealthInitializing.args = {
|
||||
userName: "developer",
|
||||
username: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "initializing",
|
||||
@@ -44,7 +44,7 @@ HealthInitializing.args = {
|
||||
|
||||
export const HealthUnhealthy = Template.bind({})
|
||||
HealthUnhealthy.args = {
|
||||
userName: "developer",
|
||||
username: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "unhealthy",
|
||||
|
||||
@@ -2,6 +2,7 @@ import Button from "@material-ui/core/Button"
|
||||
import CircularProgress from "@material-ui/core/CircularProgress"
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import Tooltip from "@material-ui/core/Tooltip"
|
||||
import ComputerIcon from "@material-ui/icons/Computer"
|
||||
import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline"
|
||||
import { FC, PropsWithChildren } from "react"
|
||||
@@ -13,68 +14,92 @@ export const Language = {
|
||||
}
|
||||
|
||||
export interface AppLinkProps {
|
||||
userName: TypesGen.User["username"]
|
||||
appsHost?: string
|
||||
username: TypesGen.User["username"]
|
||||
workspaceName: TypesGen.Workspace["name"]
|
||||
agentName: TypesGen.WorkspaceAgent["name"]
|
||||
appName: TypesGen.WorkspaceApp["name"]
|
||||
appIcon?: TypesGen.WorkspaceApp["icon"]
|
||||
appCommand?: TypesGen.WorkspaceApp["command"]
|
||||
appSubdomain: TypesGen.WorkspaceApp["subdomain"]
|
||||
health: TypesGen.WorkspaceApp["health"]
|
||||
}
|
||||
|
||||
export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
|
||||
userName,
|
||||
appsHost,
|
||||
username,
|
||||
workspaceName,
|
||||
agentName,
|
||||
appName,
|
||||
appIcon,
|
||||
appCommand,
|
||||
appSubdomain,
|
||||
health,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
// The backend redirects if the trailing slash isn't included, so we add it
|
||||
// here to avoid extra roundtrips.
|
||||
let href = `/@${userName}/${workspaceName}.${agentName}/apps/${encodeURIComponent(appName)}/`
|
||||
let href = `/@${username}/${workspaceName}.${agentName}/apps/${encodeURIComponent(appName)}/`
|
||||
if (appCommand) {
|
||||
href = `/@${userName}/${workspaceName}.${agentName}/terminal?command=${encodeURIComponent(
|
||||
href = `/@${username}/${workspaceName}.${agentName}/terminal?command=${encodeURIComponent(
|
||||
appCommand,
|
||||
)}`
|
||||
}
|
||||
if (appsHost && appSubdomain) {
|
||||
const subdomain = `${appName}--${agentName}--${workspaceName}--${username}`
|
||||
href = `${window.location.protocol}//${subdomain}.${appsHost}/`
|
||||
}
|
||||
|
||||
let canClick = true
|
||||
let icon = appIcon ? <img alt={`${appName} Icon`} src={appIcon} /> : <ComputerIcon />
|
||||
let tooltip = ""
|
||||
if (health === "initializing") {
|
||||
canClick = false
|
||||
icon = <CircularProgress size={16} />
|
||||
tooltip = "Initializing..."
|
||||
}
|
||||
if (health === "unhealthy") {
|
||||
canClick = false
|
||||
icon = <ErrorOutlineIcon className={styles.unhealthyIcon} />
|
||||
tooltip = "Unhealthy"
|
||||
}
|
||||
if (!appsHost && appSubdomain) {
|
||||
canClick = false
|
||||
icon = <ErrorOutlineIcon className={styles.notConfiguredIcon} />
|
||||
tooltip = "Your admin has not configured subdomain application access"
|
||||
}
|
||||
|
||||
const button = (
|
||||
<Button size="small" startIcon={icon} className={styles.button} disabled={!canClick}>
|
||||
{appName}
|
||||
</Button>
|
||||
)
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
target="_blank"
|
||||
className={canClick ? styles.link : styles.disabledLink}
|
||||
onClick={
|
||||
canClick
|
||||
? (event) => {
|
||||
event.preventDefault()
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(appName, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Button size="small" startIcon={icon} className={styles.button} disabled={!canClick}>
|
||||
{appName}
|
||||
</Button>
|
||||
</Link>
|
||||
<Tooltip title={tooltip}>
|
||||
<span>
|
||||
<Link
|
||||
href={href}
|
||||
target="_blank"
|
||||
className={canClick ? styles.link : styles.disabledLink}
|
||||
onClick={
|
||||
canClick
|
||||
? (event) => {
|
||||
event.preventDefault()
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(appName, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{button}
|
||||
</Link>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,4 +120,8 @@ const useStyles = makeStyles((theme) => ({
|
||||
unhealthyIcon: {
|
||||
color: theme.palette.warning.light,
|
||||
},
|
||||
|
||||
notConfiguredIcon: {
|
||||
color: theme.palette.grey[300],
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -175,10 +175,12 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
|
||||
{agent.apps.map((app) => (
|
||||
<AppLink
|
||||
key={app.name}
|
||||
appsHost={applicationsHost}
|
||||
appIcon={app.icon}
|
||||
appName={app.name}
|
||||
appCommand={app.command}
|
||||
userName={workspace.owner_name}
|
||||
appSubdomain={app.subdomain}
|
||||
username={workspace.owner_name}
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
health={app.health}
|
||||
|
||||
@@ -193,6 +193,7 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = {
|
||||
id: "test-app",
|
||||
name: "test-app",
|
||||
icon: "",
|
||||
subdomain: false,
|
||||
health: "disabled",
|
||||
healthcheck: {
|
||||
url: "",
|
||||
|
||||
Reference in New Issue
Block a user