Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1f4a48f

Browse files
committed
feat: support agent metadata terminals
1 parent 6214117 commit 1f4a48f

File tree

6 files changed

+268
-9
lines changed

6 files changed

+268
-9
lines changed

examples/templates/jfrog/docker/main.tf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ resource "coder_agent" "main" {
9898
index-url = https://${local.artifactory_username}:${artifactory_scoped_token.me.access_token}@${var.jfrog_host}/artifactory/api/pypi/${local.artifactory_repository_keys["python"]}/simple
9999
EOF
100100
101+
# project is a small Go project for jf audit to analyze.
102+
cd ~
103+
mkdir -p project
104+
git clone https://github.com/coder/retry project
101105
EOT
102106
# Set GOPROXY to use the Artifactory "go" repository.
103107
env = {
@@ -109,6 +113,26 @@ resource "coder_agent" "main" {
109113
JFROG_IDE_ACCESS_TOKEN : "${artifactory_scoped_token.me.access_token}"
110114
JFROG_IDE_STORE_CONNECTION : "true"
111115
}
116+
117+
metadata {
118+
key = "cpu"
119+
display_name = "CPU"
120+
script = "coder stat cpu"
121+
timeout = 1
122+
interval = 1
123+
}
124+
125+
metadata {
126+
key = "jfrog"
127+
display_name = "terminal:JFrog Audit"
128+
script = <<-EOT
129+
export CI=true
130+
cd project
131+
script -qec 'jf audit' /dev/null
132+
EOT
133+
timeout = 300
134+
interval = 300
135+
}
112136
}
113137

114138
resource "coder_app" "code-server" {

site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@
103103
"xterm": "5.2.1",
104104
"xterm-addon-canvas": "0.4.0",
105105
"xterm-addon-fit": "0.7.0",
106+
"xterm-addon-unicode11": "0.5.0",
106107
"xterm-addon-web-links": "0.8.0",
108+
"xterm-addon-webgl": "0.15.0",
107109
"yup": "1.2.0"
108110
},
109111
"devDependencies": {

site/pnpm-lock.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/components/Resources/AgentMetadata.stories.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,21 @@ Example.args = {
105105
},
106106
],
107107
}
108+
109+
export const Terminal = Template.bind({})
110+
Terminal.args = {
111+
metadata: [
112+
{
113+
result: {
114+
...resultDefaults,
115+
value: "\x1b[32mHello, World!\x1b[0m",
116+
},
117+
description: {
118+
...descriptionDefaults,
119+
display_name: "terminal:hello world",
120+
key: "term",
121+
script: "echo hello world",
122+
},
123+
},
124+
],
125+
}

site/src/components/Resources/AgentMetadata.tsx

Lines changed: 196 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,152 @@
1+
import Box, { BoxProps } from "@mui/material/Box"
2+
import Popover from "@mui/material/Popover"
3+
import Skeleton from "@mui/material/Skeleton"
4+
import Tooltip from "@mui/material/Tooltip"
15
import makeStyles from "@mui/styles/makeStyles"
26
import { watchAgentMetadata } from "api/api"
3-
import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated"
7+
import {
8+
WorkspaceAgent,
9+
WorkspaceAgentMetadata,
10+
WorkspaceAgentMetadataResult,
11+
} from "api/typesGenerated"
412
import { Stack } from "components/Stack/Stack"
513
import dayjs from "dayjs"
614
import {
7-
createContext,
815
FC,
16+
createContext,
917
useContext,
1018
useEffect,
1119
useRef,
1220
useState,
1321
} from "react"
14-
import Skeleton from "@mui/material/Skeleton"
22+
import { colors } from "theme/colors"
1523
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
1624
import { combineClasses } from "utils/combineClasses"
17-
import Tooltip from "@mui/material/Tooltip"
18-
import Box, { BoxProps } from "@mui/material/Box"
25+
import * as XTerm from "xterm"
26+
import { FitAddon } from "xterm-addon-fit"
27+
import { WebglAddon } from "xterm-addon-webgl"
28+
import { Unicode11Addon } from "xterm-addon-unicode11"
29+
30+
import "xterm/css/xterm.css"
1931

2032
type ItemStatus = "stale" | "valid" | "loading"
2133

2234
export const WatchAgentMetadataContext = createContext(watchAgentMetadata)
2335

36+
const MetadataTerminalPopover: FC<{
37+
id: string
38+
result: WorkspaceAgentMetadataResult
39+
}> = ({ id, result }) => {
40+
const styles = useStyles()
41+
42+
const viewTermRef = useRef<HTMLDivElement>(null)
43+
const [open, setOpen] = useState(false)
44+
45+
const [xtermRef, setXtermRef] = useState<HTMLDivElement | null>(null)
46+
const [terminal, setTerminal] = useState<XTerm.Terminal | null>(null)
47+
const [fitAddon, setFitAddon] = useState<FitAddon | null>(null)
48+
49+
const writeTerminal = () => {
50+
if (!terminal || !fitAddon) {
51+
return
52+
}
53+
54+
// We write the clearCode with the new value to avoid a flash of blankness
55+
// when the result value updates.
56+
const clearCode = "\x1B[2J\x1B[H"
57+
terminal.write(clearCode + result.value, () => {
58+
fitAddon.fit()
59+
})
60+
}
61+
62+
// Create the terminal.
63+
// Largely taken from TerminalPage.
64+
useEffect(() => {
65+
if (!xtermRef) {
66+
return
67+
}
68+
const terminal = new XTerm.Terminal({
69+
allowTransparency: true,
70+
allowProposedApi: true,
71+
disableStdin: true,
72+
fontFamily: MONOSPACE_FONT_FAMILY,
73+
fontSize: 16,
74+
theme: {
75+
background: colors.gray[16],
76+
},
77+
})
78+
terminal.loadAddon(new WebglAddon())
79+
terminal.loadAddon(new FitAddon())
80+
81+
// This addon fixes multi-width codepoint rendering such as
82+
// 🟢.
83+
terminal.loadAddon(new Unicode11Addon())
84+
terminal.unicode.activeVersion = "11"
85+
86+
const fitAddon = new FitAddon()
87+
setTerminal(terminal)
88+
setFitAddon(fitAddon)
89+
terminal.open(xtermRef)
90+
writeTerminal()
91+
92+
// This listener doesn't appear to have an effect, but it's used
93+
// in TerminalPage.tsx so I've copied it here.
94+
const listener = () => {
95+
fitAddon.fit()
96+
}
97+
window.addEventListener("resize", listener)
98+
99+
// TODO: this is obviously immensely janky. But, it's the only
100+
// way I'm able to get the text to render correctly.
101+
const resizeInterval = setInterval(() => {
102+
window.dispatchEvent(new Event("resize"))
103+
}, 100)
104+
105+
return () => {
106+
window.removeEventListener("resize", listener)
107+
clearInterval(resizeInterval)
108+
terminal.dispose()
109+
}
110+
}, [xtermRef, open])
111+
112+
useEffect(() => {
113+
writeTerminal()
114+
}, [xtermRef, open, result])
115+
116+
return (
117+
<>
118+
<div
119+
className={styles.viewTerminal}
120+
ref={viewTermRef}
121+
onMouseOver={() => {
122+
setOpen(true)
123+
}}
124+
>
125+
View Terminal
126+
</div>
127+
128+
<Popover
129+
id={id}
130+
open={open}
131+
onClose={() => setOpen(false)}
132+
anchorEl={viewTermRef.current}
133+
anchorOrigin={{
134+
vertical: "bottom",
135+
horizontal: "left",
136+
}}
137+
>
138+
<div
139+
className={styles.terminal}
140+
ref={(el) => {
141+
setXtermRef(el)
142+
}}
143+
data-testid="terminal"
144+
/>
145+
</Popover>
146+
</>
147+
)
148+
}
149+
24150
const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
25151
const styles = useStyles()
26152

@@ -31,6 +157,13 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
31157
throw new Error("Metadata item description is undefined")
32158
}
33159

160+
const terminalPrefix = "terminal:"
161+
const isTerminal = item.description.display_name.startsWith(terminalPrefix)
162+
163+
const displayName = isTerminal
164+
? item.description.display_name.slice(terminalPrefix.length)
165+
: item.description.display_name
166+
34167
const staleThreshold = Math.max(
35168
item.description.interval + item.description.timeout * 2,
36169
// In case there is intense backpressure, we give a little bit of slack.
@@ -88,10 +221,15 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
88221

89222
return (
90223
<div className={styles.metadata}>
91-
<div className={styles.metadataLabel}>
92-
{item.description.display_name}
93-
</div>
94-
<Box>{value}</Box>
224+
<div className={styles.metadataLabel}>{displayName}</div>
225+
{isTerminal ? (
226+
<MetadataTerminalPopover
227+
id={`metadata-terminal-${item.description.key}`}
228+
result={item.result}
229+
/>
230+
) : (
231+
<Box>{value}</Box>
232+
)}
95233
</div>
96234
)
97235
}
@@ -105,6 +243,7 @@ export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
105243
if (metadata.length === 0) {
106244
return <></>
107245
}
246+
108247
return (
109248
<div className={styles.root}>
110249
<Stack alignItems="baseline" direction="row" spacing={6}>
@@ -228,6 +367,54 @@ const useStyles = makeStyles((theme) => ({
228367
scrollPadding: theme.spacing(0, 4),
229368
},
230369

370+
viewTerminal: {
371+
fontFamily: MONOSPACE_FONT_FAMILY,
372+
display: "inline-block",
373+
textDecoration: "underline",
374+
fontWeight: 600,
375+
margin: 0,
376+
fontSize: 14,
377+
borderRadius: 4,
378+
color: theme.palette.text.primary,
379+
},
380+
381+
terminal: {
382+
width: "80ch",
383+
height: "30vh",
384+
padding: theme.spacing(1),
385+
// overflow: "hidden",
386+
backgroundColor: theme.palette.background.paper,
387+
flex: 1,
388+
// These styles attempt to mimic the VS Code scrollbar.
389+
"& .xterm": {
390+
padding: 4,
391+
width: "100vw",
392+
height: "100vh",
393+
},
394+
"& .xterm-viewport": {
395+
// This is required to force full-width on the terminal.
396+
// Otherwise there's a small white bar to the right of the scrollbar.
397+
width: "auto !important",
398+
},
399+
"& .xterm-viewport::-webkit-scrollbar": {
400+
width: "10px",
401+
},
402+
"& .xterm-viewport::-webkit-scrollbar-track": {
403+
backgroundColor: "inherit",
404+
},
405+
"& .xterm-viewport::-webkit-scrollbar-thumb": {
406+
minHeight: 20,
407+
backgroundColor: "rgba(255, 255, 255, 0.18)",
408+
},
409+
},
410+
411+
popover: {
412+
padding: 0,
413+
width: theme.spacing(38),
414+
color: theme.palette.text.secondary,
415+
marginTop: theme.spacing(0.5),
416+
},
417+
231418
metadata: {
232419
fontSize: 12,
233420
lineHeight: "normal",

site/src/pages/TerminalPage/TerminalPage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid"
99
import * as XTerm from "xterm"
1010
import { CanvasAddon } from "xterm-addon-canvas"
1111
import { FitAddon } from "xterm-addon-fit"
12+
import { Unicode11Addon } from "xterm-addon-unicode11"
1213
import { WebLinksAddon } from "xterm-addon-web-links"
1314
import "xterm/css/xterm.css"
1415
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
@@ -176,6 +177,7 @@ const TerminalPage: FC<TerminalPageProps> = ({ renderer }) => {
176177
return
177178
}
178179
const terminal = new XTerm.Terminal({
180+
allowProposedApi: true,
179181
allowTransparency: true,
180182
disableStdin: false,
181183
fontFamily: MONOSPACE_FONT_FAMILY,
@@ -191,6 +193,10 @@ const TerminalPage: FC<TerminalPageProps> = ({ renderer }) => {
191193
const fitAddon = new FitAddon()
192194
setFitAddon(fitAddon)
193195
terminal.loadAddon(fitAddon)
196+
// This addon fixes multi-width codepoint rendering such as
197+
// 🔵.
198+
terminal.loadAddon(new Unicode11Addon())
199+
terminal.unicode.activeVersion = "11"
194200
terminal.loadAddon(
195201
new WebLinksAddon((_, uri) => {
196202
handleWebLink(uri)

0 commit comments

Comments
 (0)