mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
feat: use wildcard url for local links in the web terminal (#6070)
This commit is contained in:
@ -26,7 +26,7 @@ export interface PortForwardButtonProps {
|
||||
agentId: string
|
||||
}
|
||||
|
||||
const portForwardURL = (
|
||||
export const portForwardURL = (
|
||||
host: string,
|
||||
port: number,
|
||||
agentName: string,
|
||||
|
@ -293,7 +293,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
</div>
|
||||
|
||||
<div className={styles.editorPane}>
|
||||
<div className={styles.editor}>
|
||||
<div className={styles.editor} data-chromatic="ignore">
|
||||
{activeFile ? (
|
||||
<MonacoEditor
|
||||
value={activeFile?.content}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { portForwardURL } from "components/PortForwardButton/PortForwardButton"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { FC, useEffect, useRef, useState } from "react"
|
||||
import { FC, useCallback, useEffect, useRef, useState } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom"
|
||||
import { colors } from "theme/colors"
|
||||
@ -95,9 +96,55 @@ const TerminalPage: FC<
|
||||
workspaceAgentError,
|
||||
workspaceAgent,
|
||||
websocketError,
|
||||
applicationsHost,
|
||||
} = terminalState.context
|
||||
const reloading = useReloading(isDisconnected)
|
||||
|
||||
// handleWebLink handles opening of URLs in the terminal!
|
||||
const handleWebLink = useCallback(
|
||||
(uri: string) => {
|
||||
if (!workspaceAgent || !workspace || !username || !applicationsHost) {
|
||||
return
|
||||
}
|
||||
|
||||
const open = (uri: string) => {
|
||||
// Copied from: https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-web-links/src/WebLinksAddon.ts#L23
|
||||
const newWindow = window.open()
|
||||
if (newWindow) {
|
||||
try {
|
||||
newWindow.opener = null
|
||||
} catch {
|
||||
// no-op, Electron can throw
|
||||
}
|
||||
newWindow.location.href = uri
|
||||
} else {
|
||||
console.warn("Opening link blocked as opener could not be cleared")
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(uri)
|
||||
const localHosts = ["0.0.0.0", "127.0.0.1", "localhost"]
|
||||
if (!localHosts.includes(url.hostname)) {
|
||||
open(uri)
|
||||
return
|
||||
}
|
||||
open(
|
||||
portForwardURL(
|
||||
applicationsHost,
|
||||
parseInt(url.port),
|
||||
workspaceAgent.name,
|
||||
workspace,
|
||||
username,
|
||||
),
|
||||
)
|
||||
} catch (ex) {
|
||||
open(uri)
|
||||
}
|
||||
},
|
||||
[workspaceAgent, workspace, username, applicationsHost],
|
||||
)
|
||||
|
||||
// Create the terminal!
|
||||
useEffect(() => {
|
||||
if (!xtermRef.current) {
|
||||
@ -116,7 +163,11 @@ const TerminalPage: FC<
|
||||
const fitAddon = new FitAddon()
|
||||
setFitAddon(fitAddon)
|
||||
terminal.loadAddon(fitAddon)
|
||||
terminal.loadAddon(new WebLinksAddon())
|
||||
terminal.loadAddon(
|
||||
new WebLinksAddon((_, uri) => {
|
||||
handleWebLink(uri)
|
||||
}),
|
||||
)
|
||||
terminal.onData((data) => {
|
||||
sendEvent({
|
||||
type: "WRITE",
|
||||
@ -145,7 +196,7 @@ const TerminalPage: FC<
|
||||
window.removeEventListener("resize", listener)
|
||||
terminal.dispose()
|
||||
}
|
||||
}, [renderer, sendEvent, xtermRef])
|
||||
}, [renderer, sendEvent, xtermRef, handleWebLink])
|
||||
|
||||
// Triggers the initial terminal connection using
|
||||
// the reconnection token and workspace name found
|
||||
|
@ -10,6 +10,7 @@ export interface TerminalContext {
|
||||
workspaceAgentError?: Error | unknown
|
||||
websocket?: WebSocket
|
||||
websocketError?: Error | unknown
|
||||
applicationsHost?: string
|
||||
|
||||
// Assigned by connecting!
|
||||
// The workspace agent is entirely optional. If the agent is omitted the
|
||||
@ -47,6 +48,9 @@ export const terminalMachine =
|
||||
getWorkspace: {
|
||||
data: TypesGen.Workspace
|
||||
}
|
||||
getApplicationsHost: {
|
||||
data: TypesGen.AppHostResponse
|
||||
}
|
||||
getWorkspaceAgent: {
|
||||
data: TypesGen.WorkspaceAgent
|
||||
}
|
||||
@ -55,24 +59,61 @@ export const terminalMachine =
|
||||
}
|
||||
},
|
||||
},
|
||||
initial: "gettingWorkspace",
|
||||
initial: "setup",
|
||||
states: {
|
||||
gettingWorkspace: {
|
||||
invoke: {
|
||||
src: "getWorkspace",
|
||||
id: "getWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignWorkspace", "clearWorkspaceError"],
|
||||
target: "gettingWorkspaceAgent",
|
||||
setup: {
|
||||
type: "parallel",
|
||||
states: {
|
||||
getApplicationsHost: {
|
||||
initial: "gettingApplicationsHost",
|
||||
states: {
|
||||
gettingApplicationsHost: {
|
||||
invoke: {
|
||||
src: "getApplicationsHost",
|
||||
id: "getApplicationsHost",
|
||||
onDone: {
|
||||
actions: [
|
||||
"assignApplicationsHost",
|
||||
"clearApplicationsHostError",
|
||||
],
|
||||
target: "success",
|
||||
},
|
||||
},
|
||||
},
|
||||
success: {
|
||||
type: "final",
|
||||
},
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignWorkspaceError",
|
||||
target: "disconnected",
|
||||
},
|
||||
getWorkspace: {
|
||||
initial: "gettingWorkspace",
|
||||
states: {
|
||||
gettingWorkspace: {
|
||||
invoke: {
|
||||
src: "getWorkspace",
|
||||
id: "getWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignWorkspace", "clearWorkspaceError"],
|
||||
target: "success",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignWorkspaceError",
|
||||
target: "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
success: {
|
||||
type: "final",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
onDone: {
|
||||
target: "gettingWorkspaceAgent",
|
||||
},
|
||||
},
|
||||
gettingWorkspaceAgent: {
|
||||
@ -129,7 +170,7 @@ export const terminalMachine =
|
||||
on: {
|
||||
CONNECT: {
|
||||
actions: "assignConnection",
|
||||
target: "gettingWorkspace",
|
||||
target: "gettingWorkspaceAgent",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -146,6 +187,9 @@ export const terminalMachine =
|
||||
context.workspaceName,
|
||||
)
|
||||
},
|
||||
getApplicationsHost: async () => {
|
||||
return API.getApplicationsHost()
|
||||
},
|
||||
getWorkspaceAgent: async (context) => {
|
||||
if (!context.workspace || !context.workspaceName) {
|
||||
throw new Error("workspace or workspace name is not set")
|
||||
@ -218,6 +262,13 @@ export const terminalMachine =
|
||||
...context,
|
||||
workspaceError: undefined,
|
||||
})),
|
||||
assignApplicationsHost: assign({
|
||||
applicationsHost: (_, { data }) => data.host,
|
||||
}),
|
||||
clearApplicationsHostError: assign((context) => ({
|
||||
...context,
|
||||
applicationsHostError: undefined,
|
||||
})),
|
||||
assignWorkspaceAgent: assign({
|
||||
workspaceAgent: (_, event) => event.data,
|
||||
}),
|
||||
|
Reference in New Issue
Block a user