Skip to content

Commit 4061b13

Browse files
committed
Finish UI display
1 parent adb06ea commit 4061b13

File tree

4 files changed

+192
-45
lines changed

4 files changed

+192
-45
lines changed

site/src/components/Logs/Logs.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ComponentMeta, Story } from "@storybook/react"
2+
import { LogLevel } from "api/typesGenerated"
23
import { MockWorkspaceBuildLogs } from "../../testHelpers/entities"
34
import { Logs, LogsProps } from "./Logs"
45

@@ -12,8 +13,15 @@ const Template: Story<LogsProps> = (args) => <Logs {...args} />
1213
const lines = MockWorkspaceBuildLogs.map((log) => ({
1314
time: log.created_at,
1415
output: log.output,
16+
level: "info" as LogLevel,
1517
}))
1618
export const Example = Template.bind({})
1719
Example.args = {
1820
lines,
1921
}
22+
23+
export const WithLineNumbers = Template.bind({})
24+
WithLineNumbers.args = {
25+
lines,
26+
lineNumbers: true,
27+
}

site/src/components/Logs/Logs.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { makeStyles } from "@material-ui/core/styles"
1+
import { makeStyles, Theme } from "@material-ui/core/styles"
22
import { LogLevel } from "api/typesGenerated"
33
import dayjs from "dayjs"
44
import { FC } from "react"
55
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
66
import { combineClasses } from "../../util/combineClasses"
77

8-
interface Line {
8+
export interface Line {
99
time: string
1010
output: string
1111
level: LogLevel
@@ -14,15 +14,19 @@ interface Line {
1414
export interface LogsProps {
1515
lines: Line[]
1616
hideTimestamps?: boolean
17+
lineNumbers?: boolean
1718
className?: string
1819
}
1920

2021
export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
2122
hideTimestamps,
2223
lines,
24+
lineNumbers,
2325
className = "",
2426
}) => {
25-
const styles = useStyles()
27+
const styles = useStyles({
28+
lineNumbers: Boolean(lineNumbers),
29+
})
2630

2731
return (
2832
<div className={combineClasses([className, styles.root])}>
@@ -32,7 +36,9 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
3236
{!hideTimestamps && (
3337
<>
3438
<span className={styles.time}>
35-
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
39+
{lineNumbers
40+
? idx + 1
41+
: dayjs(line.time).format(`HH:mm:ss.SSS`)}
3642
</span>
3743
<span className={styles.space}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
3844
</>
@@ -45,7 +51,12 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
4551
)
4652
}
4753

48-
const useStyles = makeStyles((theme) => ({
54+
const useStyles = makeStyles<
55+
Theme,
56+
{
57+
lineNumbers: boolean
58+
}
59+
>((theme) => ({
4960
root: {
5061
minHeight: 156,
5162
background: theme.palette.background.default,
@@ -62,7 +73,7 @@ const useStyles = makeStyles((theme) => ({
6273
},
6374
line: {
6475
// Whitespace is significant in terminal output for alignment
65-
whiteSpace: "pre",
76+
whiteSpace: "pre-line",
6677
padding: theme.spacing(0, 3),
6778

6879
"&.error": {
@@ -78,7 +89,8 @@ const useStyles = makeStyles((theme) => ({
7889
},
7990
time: {
8091
userSelect: "none",
81-
width: theme.spacing(12.5),
92+
width: ({ lineNumbers }) => theme.spacing(lineNumbers ? 3 : 12.5),
93+
whiteSpace: "pre",
8294
display: "inline-block",
8395
color: theme.palette.text.secondary,
8496
},

site/src/components/Resources/AgentRow.tsx

Lines changed: 157 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
1-
import { makeStyles } from "@material-ui/core/styles"
1+
import Link from "@material-ui/core/Link"
2+
import Popover from "@material-ui/core/Popover"
3+
import { makeStyles, useTheme } from "@material-ui/core/styles"
4+
import PlayCircleOutlined from "@material-ui/icons/PlayCircleFilledOutlined"
5+
import VisibilityOffOutlined from "@material-ui/icons/VisibilityOffOutlined"
6+
import VisibilityOutlined from "@material-ui/icons/VisibilityOutlined"
27
import { Skeleton } from "@material-ui/lab"
8+
import { useMachine } from "@xstate/react"
9+
import { AppLinkSkeleton } from "components/AppLink/AppLinkSkeleton"
10+
import { Maybe } from "components/Conditionals/Maybe"
11+
import { Line, Logs } from "components/Logs/Logs"
312
import { PortForwardButton } from "components/PortForwardButton/PortForwardButton"
4-
import { FC } from "react"
13+
import { VSCodeDesktopButton } from "components/VSCodeDesktopButton/VSCodeDesktopButton"
14+
import { FC, useEffect, useRef, useState } from "react"
15+
import { useTranslation } from "react-i18next"
16+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
17+
import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism"
18+
import { workspaceAgentLogsMachine } from "xServices/workspaceAgentLogs/workspaceAgentLogsXService"
519
import { Workspace, WorkspaceAgent } from "../../api/typesGenerated"
620
import { AppLink } from "../AppLink/AppLink"
721
import { SSHButton } from "../SSHButton/SSHButton"
822
import { Stack } from "../Stack/Stack"
923
import { TerminalLink } from "../TerminalLink/TerminalLink"
1024
import { AgentLatency } from "./AgentLatency"
11-
import { AgentVersion } from "./AgentVersion"
12-
import { Maybe } from "components/Conditionals/Maybe"
1325
import { AgentStatus } from "./AgentStatus"
14-
import { AppLinkSkeleton } from "components/AppLink/AppLinkSkeleton"
15-
import { useTranslation } from "react-i18next"
16-
import { VSCodeDesktopButton } from "components/VSCodeDesktopButton/VSCodeDesktopButton"
17-
import { useMachine } from "@xstate/react"
18-
import { workspaceAgentLogsMachine } from "xServices/workspaceAgentLogs/workspaceAgentLogsXService"
19-
import { Line, Logs } from "components/Logs/Logs"
20-
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
21-
import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism"
26+
import { AgentVersion } from "./AgentVersion"
2227

2328
export interface AgentRowProps {
2429
agent: WorkspaceAgent
@@ -43,14 +48,33 @@ export const AgentRow: FC<AgentRowProps> = ({
4348
}) => {
4449
const styles = useStyles()
4550
const { t } = useTranslation("agent")
46-
const [logsMachine] = useMachine(workspaceAgentLogsMachine, {
51+
const [logsMachine, sendLogsEvent] = useMachine(workspaceAgentLogsMachine, {
4752
context: { agentID: agent.id },
4853
})
49-
50-
console.log("drac", darcula)
54+
const theme = useTheme()
55+
const startupScriptAnchorRef = useRef<HTMLLinkElement>(null)
56+
const [startupScriptOpen, setStartupScriptOpen] = useState(false)
57+
const [showStartupLogs, setShowStartupLogs] = useState(
58+
agent.lifecycle_state !== "ready",
59+
)
60+
useEffect(() => {
61+
setShowStartupLogs(agent.lifecycle_state !== "ready")
62+
}, [agent.lifecycle_state])
63+
useEffect(() => {
64+
// We only want to fetch logs when they are actually shown,
65+
// otherwise we can make a lot of requests that aren't necessary.
66+
if (showStartupLogs) {
67+
sendLogsEvent("FETCH_STARTUP_LOGS")
68+
}
69+
}, [sendLogsEvent, showStartupLogs])
5170

5271
return (
53-
<Stack direction="column" key={agent.id} spacing={0}>
72+
<Stack
73+
direction="column"
74+
key={agent.id}
75+
spacing={0}
76+
className={styles.agentWrapper}
77+
>
5478
<Stack
5579
direction="row"
5680
alignItems="center"
@@ -91,6 +115,79 @@ export const AgentRow: FC<AgentRowProps> = ({
91115
{t("unableToConnect")}
92116
</Maybe>
93117
</Stack>
118+
119+
<Stack
120+
direction="row"
121+
alignItems="baseline"
122+
spacing={1}
123+
className={styles.startupLinks}
124+
>
125+
{(logsMachine.context.startupLogs || agent.startup_script) && (
126+
<Link
127+
className={styles.startupLink}
128+
variant="body2"
129+
onClick={() => {
130+
setShowStartupLogs(!showStartupLogs)
131+
}}
132+
>
133+
{showStartupLogs ? (
134+
<VisibilityOffOutlined />
135+
) : (
136+
<VisibilityOutlined />
137+
)}
138+
{showStartupLogs ? "Hide" : "Show"} Startup Logs
139+
</Link>
140+
)}
141+
142+
{agent.startup_script && (
143+
<Link
144+
className={styles.startupLink}
145+
variant="body2"
146+
ref={startupScriptAnchorRef}
147+
onClick={() => {
148+
setStartupScriptOpen(!startupScriptOpen)
149+
}}
150+
>
151+
<PlayCircleOutlined />
152+
View Startup Script
153+
</Link>
154+
)}
155+
156+
<Popover
157+
classes={{
158+
paper: styles.startupScriptPopover,
159+
}}
160+
open={startupScriptOpen}
161+
onClose={() => setStartupScriptOpen(false)}
162+
anchorEl={startupScriptAnchorRef.current}
163+
anchorOrigin={{
164+
vertical: "bottom",
165+
horizontal: "left",
166+
}}
167+
transformOrigin={{
168+
vertical: "top",
169+
horizontal: "left",
170+
}}
171+
>
172+
<div>
173+
<SyntaxHighlighter
174+
style={darcula}
175+
language="shell"
176+
showLineNumbers
177+
// Use inline styles does not work correctly
178+
// https://github.com/react-syntax-highlighter/react-syntax-highlighter/issues/329
179+
codeTagProps={{ style: {} }}
180+
customStyle={{
181+
background: theme.palette.background.default,
182+
maxWidth: 600,
183+
margin: 0,
184+
}}
185+
>
186+
{agent.startup_script || ""}
187+
</SyntaxHighlighter>
188+
</div>
189+
</Popover>
190+
</Stack>
94191
</div>
95192
</Stack>
96193

@@ -151,45 +248,68 @@ export const AgentRow: FC<AgentRowProps> = ({
151248
)}
152249
</Stack>
153250
</Stack>
154-
155-
<div>
156-
Startup Script
157-
<SyntaxHighlighter
158-
style={darcula}
159-
language="bash"
160-
useInlineStyles={false}
161-
codeTagProps={{ style: {} }}
162-
>
163-
{String(agent.startup_script)}
164-
</SyntaxHighlighter>
165-
{logsMachine.context.startupLogs && (
166-
<Logs
167-
className={styles.agentStartupLogs}
168-
lines={logsMachine.context.startupLogs.map(
251+
{showStartupLogs && (
252+
<Logs
253+
className={styles.startupLogs}
254+
lineNumbers
255+
lines={
256+
logsMachine.context.startupLogs?.map(
169257
(log): Line => ({
170258
level: "info",
171259
output: log.output,
172260
time: log.created_at,
173261
}),
174-
)}
175-
/>
176-
)}
177-
</div>
262+
) || []
263+
}
264+
/>
265+
)}
178266
</Stack>
179267
)
180268
}
181269

182270
const useStyles = makeStyles((theme) => ({
271+
agentWrapper: {
272+
"&:not(:last-child)": {
273+
borderBottom: `1px solid ${theme.palette.divider}`,
274+
},
275+
},
276+
183277
agentRow: {
184278
padding: theme.spacing(3, 4),
185279
backgroundColor: theme.palette.background.paperLight,
186280
fontSize: 16,
281+
},
187282

188-
"&:not(:last-child)": {
189-
borderBottom: `1px solid ${theme.palette.divider}`,
283+
startupLinks: {
284+
display: "flex",
285+
alignItems: "center",
286+
gap: theme.spacing(2),
287+
marginTop: theme.spacing(0.5),
288+
},
289+
290+
startupLink: {
291+
cursor: "pointer",
292+
display: "flex",
293+
gap: 4,
294+
alignItems: "center",
295+
userSelect: "none",
296+
297+
"& svg": {
298+
width: 12,
299+
height: 12,
190300
},
191301
},
192302

303+
startupLogs: {
304+
maxHeight: 256,
305+
display: "flex",
306+
flexDirection: "column-reverse",
307+
},
308+
309+
startupScriptPopover: {
310+
backgroundColor: theme.palette.background.default,
311+
},
312+
193313
agentStatusWrapper: {
194314
width: theme.spacing(4.5),
195315
display: "flex",

site/src/xServices/workspaceAgentLogs/workspaceAgentLogsXService.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const workspaceAgentLogsMachine = createMachine(
1010
events: {} as {
1111
type: "ADD_STARTUP_LOGS"
1212
logs: TypesGen.WorkspaceAgentStartupLog[]
13+
} | {
14+
type: "FETCH_STARTUP_LOGS",
1315
},
1416
context: {} as {
1517
agentID: string
@@ -22,8 +24,13 @@ export const workspaceAgentLogsMachine = createMachine(
2224
},
2325
},
2426
tsTypes: {} as import("./workspaceAgentLogsXService.typegen").Typegen0,
25-
initial: "loading",
27+
initial: "waiting",
2628
states: {
29+
waiting: {
30+
on: {
31+
FETCH_STARTUP_LOGS: "loading",
32+
},
33+
},
2734
loading: {
2835
invoke: {
2936
src: "getStartupLogs",

0 commit comments

Comments
 (0)