Skip to content

Commit 3b8172e

Browse files
committed
Active Windows mode on Windows
1 parent 3e1a0a4 commit 3b8172e

File tree

8 files changed

+56
-63
lines changed

8 files changed

+56
-63
lines changed

agent/agent.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
484484
}
485485
a.reconnectingPTYs.Store(id, rpty)
486486
go func() {
487+
// CommandContext isn't respected for Windows PTYs right now,
488+
// so we need to manually track the lifecycle.
487489
// When the context has been completed either:
488490
// 1. The timeout completed.
489491
// 2. The parent context was canceled.

site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"react-dom": "17.0.2",
4242
"react-router-dom": "6.3.0",
4343
"swr": "1.2.2",
44+
"uuid": "^8.3.2",
4445
"xstate": "4.31.0",
4546
"xterm": "^4.18.0",
4647
"xterm-addon-fit": "^0.5.0",
@@ -65,6 +66,7 @@
6566
"@types/react": "17.0.44",
6667
"@types/react-dom": "17.0.16",
6768
"@types/superagent": "4.1.15",
69+
"@types/uuid": "^8.3.4",
6870
"@typescript-eslint/eslint-plugin": "5.21.0",
6971
"@typescript-eslint/parser": "5.21.0",
7072
"@xstate/cli": "0.1.7",

site/src/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export interface WorkspaceResource {
8787
export interface WorkspaceAgent {
8888
id: string
8989
name: string
90+
operating_system: string
9091
}
9192

9293
export interface APIKeyResponse {

site/src/pages/TerminalPage/TerminalPage.test.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { waitFor } from "@testing-library/react"
2-
import crypto from "crypto"
32
import "jest-canvas-mock"
43
import WS from "jest-websocket-mock"
54
import { rest } from "msw"
@@ -25,12 +24,6 @@ Object.defineProperty(window, "matchMedia", {
2524
})),
2625
})
2726

28-
Object.defineProperty(window, "crypto", {
29-
value: {
30-
randomUUID: () => crypto.randomUUID(),
31-
},
32-
})
33-
3427
Object.defineProperty(window, "TextEncoder", {
3528
value: TextEncoder,
3629
})

site/src/pages/TerminalPage/TerminalPage.tsx

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { makeStyles } from "@material-ui/core/styles"
22
import { useMachine } from "@xstate/react"
33
import React from "react"
44
import { useLocation, useNavigate, useParams } from "react-router-dom"
5+
import { v4 as uuidv4 } from "uuid"
56
import * as XTerm from "xterm"
67
import { FitAddon } from "xterm-addon-fit"
78
import { WebLinksAddon } from "xterm-addon-web-links"
@@ -16,14 +17,6 @@ export const Language = {
1617
websocketErrorMessagePrefix: "WebSocket failed: ",
1718
}
1819

19-
// TypeScript doesn't have the randomUUID type on Crypto yet. See:
20-
// https://github.com/denoland/deno/issues/12754#issuecomment-970386235
21-
declare global {
22-
interface Crypto {
23-
randomUUID: () => string
24-
}
25-
}
26-
2720
export const TerminalPage: React.FC<{
2821
readonly renderer?: XTerm.RendererType
2922
}> = ({ renderer }) => {
@@ -39,11 +32,7 @@ export const TerminalPage: React.FC<{
3932
// a round-trip, and must be a UUIDv4.
4033
const [reconnectionToken] = React.useState<string>(() => {
4134
const search = new URLSearchParams(location.search)
42-
let reconnect = search.get("reconnect")
43-
if (reconnect === null) {
44-
reconnect = crypto.randomUUID()
45-
}
46-
return reconnect
35+
return search.get("reconnect") ?? uuidv4()
4736
})
4837
const [terminalState, sendEvent] = useMachine(terminalMachine, {
4938
actions: {
@@ -59,6 +48,8 @@ export const TerminalPage: React.FC<{
5948
},
6049
})
6150
const isConnected = terminalState.matches("connected")
51+
const { organizationsError, workspaceError, workspaceAgentError, workspaceAgent, websocketError } =
52+
terminalState.context
6253

6354
// Create the terminal!
6455
React.useEffect(() => {
@@ -150,17 +141,17 @@ export const TerminalPage: React.FC<{
150141
terminal.options = {
151142
disableStdin: true,
152143
}
153-
if (terminalState.context.organizationsError instanceof Error) {
154-
terminal.writeln(Language.organizationsErrorMessagePrefix + terminalState.context.organizationsError.message)
144+
if (organizationsError instanceof Error) {
145+
terminal.writeln(Language.organizationsErrorMessagePrefix + organizationsError.message)
155146
}
156-
if (terminalState.context.workspaceError instanceof Error) {
157-
terminal.writeln(Language.workspaceErrorMessagePrefix + terminalState.context.workspaceError.message)
147+
if (workspaceError instanceof Error) {
148+
terminal.writeln(Language.workspaceErrorMessagePrefix + workspaceError.message)
158149
}
159-
if (terminalState.context.workspaceAgentError instanceof Error) {
160-
terminal.writeln(Language.workspaceAgentErrorMessagePrefix + terminalState.context.workspaceAgentError.message)
150+
if (workspaceAgentError instanceof Error) {
151+
terminal.writeln(Language.workspaceAgentErrorMessagePrefix + workspaceAgentError.message)
161152
}
162-
if (terminalState.context.websocketError instanceof Error) {
163-
terminal.writeln(Language.websocketErrorMessagePrefix + terminalState.context.websocketError.message)
153+
if (websocketError instanceof Error) {
154+
terminal.writeln(Language.websocketErrorMessagePrefix + websocketError.message)
164155
}
165156
return
166157
}
@@ -174,6 +165,7 @@ export const TerminalPage: React.FC<{
174165
terminal.focus()
175166
terminal.options = {
176167
disableStdin: false,
168+
windowsMode: workspaceAgent?.operating_system === "windows",
177169
}
178170

179171
// Update the terminal size post-fit.
@@ -185,10 +177,11 @@ export const TerminalPage: React.FC<{
185177
},
186178
})
187179
}, [
188-
terminalState.context.workspaceError,
189-
terminalState.context.organizationsError,
190-
terminalState.context.workspaceAgentError,
191-
terminalState.context.websocketError,
180+
workspaceError,
181+
organizationsError,
182+
workspaceAgentError,
183+
websocketError,
184+
workspaceAgent,
192185
terminal,
193186
fitAddon,
194187
isConnected,
@@ -199,7 +192,9 @@ export const TerminalPage: React.FC<{
199192
<>
200193
{/* This overlay makes it more obvious that the terminal is disconnected. */}
201194
{/* It's nice for situations where Coder restarts, and they are temporarily disconnected. */}
202-
<div className={`${styles.overlay} ${isConnected ? "connected" : ""}`} />
195+
<div className={`${styles.overlay} ${isConnected ? "connected" : ""}`}>
196+
<span>Disconnected</span>
197+
</div>
203198
<div className={styles.terminal} ref={xtermRef} data-testid="terminal" />
204199
</>
205200
)
@@ -214,6 +209,12 @@ const useStyles = makeStyles(() => ({
214209
bottom: 0,
215210
right: 0,
216211
zIndex: 1,
212+
alignItems: "center",
213+
justifyContent: "center",
214+
display: "flex",
215+
color: "white",
216+
fontFamily: MONOSPACE_FONT_FAMILY,
217+
fontSize: 18,
217218
backgroundColor: "rgba(0, 0, 0, 0.5)",
218219
"&.connected": {
219220
opacity: 0,

site/src/testHelpers/entities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export const MockWorkspace: Workspace = {
102102
export const MockWorkspaceAgent: WorkspaceAgent = {
103103
id: "test-workspace-agent",
104104
name: "a-workspace-agent",
105+
operating_system: "linux",
105106
}
106107

107108
export const MockWorkspaceResource: WorkspaceResource = {

site/src/xServices/terminal/terminalXService.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,13 @@ export const terminalMachine =
150150
{
151151
services: {
152152
getOrganizations: API.getOrganizations,
153-
getWorkspace: async (context: TerminalContext) => {
153+
getWorkspace: async (context) => {
154154
if (!context.organizations || !context.workspaceName) {
155155
throw new Error("organizations or workspace not set")
156156
}
157157
return API.getWorkspace(context.organizations[0].id, context.username, context.workspaceName)
158158
},
159-
getWorkspaceAgent: async (context: TerminalContext) => {
159+
getWorkspaceAgent: async (context) => {
160160
if (!context.workspace || !context.workspaceName) {
161161
throw new Error("workspace or workspace name is not set")
162162
}
@@ -167,38 +167,29 @@ export const terminalMachine =
167167
const agentName = workspaceNameParts[1]
168168

169169
const resources = await API.getWorkspaceResources(context.workspace.latest_build.id)
170-
for (let i = 0; i < resources.length; i++) {
171-
const resource = resources[i]
172-
if (!resource.agents) {
173-
continue
174-
}
175-
if (resource.agents.length <= 0) {
176-
continue
177-
}
178-
if (!agentName) {
179-
return resource.agents[0]
180-
}
181-
for (let a = 0; a < resource.agents.length; a++) {
182-
const agent = resource.agents[a]
183-
if (agent.name !== agentName) {
184-
continue
170+
171+
const agent = resources
172+
.map((resource) => {
173+
if (!resource.agents || resource.agents.length < 1) {
174+
return
185175
}
186-
return agent
187-
}
176+
if (!agentName) {
177+
return resource.agents[0]
178+
}
179+
return resource.agents.find((agent) => agent.name === agentName)
180+
})
181+
.filter((a) => a)[0]
182+
if (!agent) {
183+
throw new Error("no agent found with id")
188184
}
189-
throw new Error("no agent found with id")
185+
return agent
190186
},
191-
connect: (context: TerminalContext) => (send) => {
187+
connect: (context) => (send) => {
192188
return new Promise<WebSocket>((resolve, reject) => {
193189
if (!context.workspaceAgent) {
194190
return reject("workspace agent is not set")
195191
}
196-
let proto = location.protocol
197-
if (proto === "https:") {
198-
proto = "wss:"
199-
} else {
200-
proto = "ws:"
201-
}
192+
const proto = location.protocol === "https:" ? "wss:" : "ws:"
202193
const socket = new WebSocket(
203194
`${proto}//${location.host}/api/v2/workspaceagents/${context.workspaceAgent.id}/pty?reconnect=${context.reconnection}`,
204195
)
@@ -275,9 +266,6 @@ export const terminalMachine =
275266
}
276267
context.websocket.send(new TextEncoder().encode(JSON.stringify(event.request)))
277268
},
278-
readMessage: () => {
279-
// Override this with the terminal writer!
280-
},
281269
disconnect: (context: TerminalContext) => {
282270
// Code 1000 is a successful exit!
283271
context.websocket?.close(1000)

site/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,6 +3194,11 @@
31943194
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
31953195
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
31963196

3197+
"@types/uuid@^8.3.4":
3198+
version "8.3.4"
3199+
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
3200+
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
3201+
31973202
"@types/webpack-env@^1.16.0":
31983203
version "1.16.3"
31993204
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"

0 commit comments

Comments
 (0)