1
1
import Popover from "@mui/material/Popover" ;
2
2
import { makeStyles , useTheme } from "@mui/styles" ;
3
3
import Skeleton from "@mui/material/Skeleton" ;
4
- import { useMachine } from "@xstate/react " ;
4
+ import * as API from "api/api " ;
5
5
import CodeOutlined from "@mui/icons-material/CodeOutlined" ;
6
6
import {
7
7
CloseDropdown ,
8
8
OpenDropdown ,
9
9
} from "components/DropdownArrows/DropdownArrows" ;
10
- import { LogLine , logLineHeight } from "components/WorkspaceBuildLogs/Logs" ;
10
+ import {
11
+ Line ,
12
+ LogLine ,
13
+ logLineHeight ,
14
+ } from "components/WorkspaceBuildLogs/Logs" ;
11
15
import { PortForwardButton } from "./PortForwardButton" ;
12
16
import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton" ;
13
17
import {
@@ -25,15 +29,11 @@ import AutoSizer from "react-virtualized-auto-sizer";
25
29
import { FixedSizeList as List , ListOnScrollProps } from "react-window" ;
26
30
import { colors } from "theme/colors" ;
27
31
import { combineClasses } from "utils/combineClasses" ;
28
- import {
29
- LineWithID ,
30
- workspaceAgentLogsMachine ,
31
- } from "xServices/workspaceAgentLogs/workspaceAgentLogsXService" ;
32
32
import {
33
33
Workspace ,
34
34
WorkspaceAgent ,
35
35
WorkspaceAgentMetadata ,
36
- } from "../../ api/typesGenerated" ;
36
+ } from "api/typesGenerated" ;
37
37
import { AppLink } from "./AppLink/AppLink" ;
38
38
import { SSHButton } from "./SSHButton/SSHButton" ;
39
39
import { Stack } from "../Stack/Stack" ;
@@ -44,6 +44,14 @@ import { AgentVersion } from "./AgentVersion";
44
44
import { AgentStatus } from "./AgentStatus" ;
45
45
import Collapse from "@mui/material/Collapse" ;
46
46
import { useProxy } from "contexts/ProxyContext" ;
47
+ import { displayError } from "components/GlobalSnackbar/utils" ;
48
+
49
+ // Logs are stored as the Line interface to make rendering
50
+ // much more efficient. Instead of mapping objects each time, we're
51
+ // able to just pass the array of logs to the component.
52
+ export interface LineWithID extends Line {
53
+ id : number ;
54
+ }
47
55
48
56
export interface AgentRowProps {
49
57
agent : WorkspaceAgent ;
@@ -68,24 +76,11 @@ export const AgentRow: FC<AgentRowProps> = ({
68
76
hideVSCodeDesktopButton,
69
77
serverVersion,
70
78
onUpdateAgent,
71
- storybookLogs,
72
79
storybookAgentMetadata,
73
80
sshPrefix,
81
+ storybookLogs,
74
82
} ) => {
75
83
const styles = useStyles ( ) ;
76
- const [ logsMachine , sendLogsEvent ] = useMachine ( workspaceAgentLogsMachine , {
77
- context : { agentID : agent . id } ,
78
- services : process . env . STORYBOOK
79
- ? {
80
- getLogs : async ( ) => {
81
- return storybookLogs || [ ] ;
82
- } ,
83
- streamLogs : ( ) => async ( ) => {
84
- // noop
85
- } ,
86
- }
87
- : undefined ,
88
- } ) ;
89
84
const theme = useTheme ( ) ;
90
85
const startupScriptAnchorRef = useRef < HTMLButtonElement > ( null ) ;
91
86
const [ startupScriptOpen , setStartupScriptOpen ] = useState ( false ) ;
@@ -94,36 +89,20 @@ export const AgentRow: FC<AgentRowProps> = ({
94
89
showApps &&
95
90
( ( agent . status === "connected" && hasAppsToDisplay ) ||
96
91
agent . status === "connecting" ) ;
97
- const hasStartupFeatures =
98
- Boolean ( agent . logs_length ) || Boolean ( logsMachine . context . logs ?. length ) ;
92
+ const hasStartupFeatures = Boolean ( agent . logs_length ) ;
99
93
const { proxy } = useProxy ( ) ;
100
-
101
94
const [ showLogs , setShowLogs ] = useState (
102
95
[ "starting" , "start_timeout" ] . includes ( agent . lifecycle_state ) &&
103
96
hasStartupFeatures ,
104
97
) ;
105
- useEffect ( ( ) => {
106
- setShowLogs ( agent . lifecycle_state !== "ready" && hasStartupFeatures ) ;
107
- } , [ agent . lifecycle_state , hasStartupFeatures ] ) ;
108
- // External applications can provide startup logs for an agent during it's spawn.
109
- // These could be Kubernetes logs, or other logs that are useful to the user.
110
- // For this reason, we want to fetch these logs when the agent is starting.
111
- useEffect ( ( ) => {
112
- if ( agent . lifecycle_state === "starting" ) {
113
- sendLogsEvent ( "FETCH_LOGS" ) ;
114
- }
115
- } , [ sendLogsEvent , agent . lifecycle_state ] ) ;
116
- useEffect ( ( ) => {
117
- // We only want to fetch logs when they are actually shown,
118
- // otherwise we can make a lot of requests that aren't necessary.
119
- if ( showLogs && logsMachine . can ( "FETCH_LOGS" ) ) {
120
- sendLogsEvent ( "FETCH_LOGS" ) ;
121
- }
122
- } , [ logsMachine , sendLogsEvent , showLogs ] ) ;
98
+ const agentLogs = useAgentLogs ( agent . id , {
99
+ enabled : showLogs ,
100
+ initialData : process . env . STORYBOOK ? storybookLogs || [ ] : undefined ,
101
+ } ) ;
123
102
const logListRef = useRef < List > ( null ) ;
124
103
const logListDivRef = useRef < HTMLDivElement > ( null ) ;
125
104
const startupLogs = useMemo ( ( ) => {
126
- const allLogs = logsMachine . context . logs || [ ] ;
105
+ const allLogs = agentLogs || [ ] ;
127
106
128
107
const logs = [ ...allLogs ] ;
129
108
if ( agent . logs_overflowed ) {
@@ -135,8 +114,13 @@ export const AgentRow: FC<AgentRowProps> = ({
135
114
} ) ;
136
115
}
137
116
return logs ;
138
- } , [ logsMachine . context . logs , agent . logs_overflowed ] ) ;
117
+ } , [ agentLogs , agent . logs_overflowed ] ) ;
139
118
const [ bottomOfLogs , setBottomOfLogs ] = useState ( true ) ;
119
+
120
+ useEffect ( ( ) => {
121
+ setShowLogs ( agent . lifecycle_state !== "ready" && hasStartupFeatures ) ;
122
+ } , [ agent . lifecycle_state , hasStartupFeatures ] ) ;
123
+
140
124
// This is a layout effect to remove flicker when we're scrolling to the bottom.
141
125
useLayoutEffect ( ( ) => {
142
126
// If we're currently watching the bottom, we always want to stay at the bottom.
@@ -396,6 +380,51 @@ export const AgentRow: FC<AgentRowProps> = ({
396
380
) ;
397
381
} ;
398
382
383
+ const useAgentLogs = (
384
+ agentId : string ,
385
+ { enabled, initialData } : { enabled : boolean ; initialData ?: LineWithID [ ] } ,
386
+ ) => {
387
+ const [ logs , setLogs ] = useState < LineWithID [ ] | undefined > ( initialData ) ;
388
+ const socket = useRef < WebSocket | null > ( null ) ;
389
+
390
+ useEffect ( ( ) => {
391
+ if ( ! enabled ) {
392
+ socket . current ?. close ( ) ;
393
+ return ;
394
+ }
395
+
396
+ socket . current = API . watchWorkspaceAgentLogs ( agentId , {
397
+ // Get all logs
398
+ after : 0 ,
399
+ onMessage : ( logs ) => {
400
+ setLogs ( ( previousLogs ) => {
401
+ const newLogs : LineWithID [ ] = logs . map ( ( log ) => ( {
402
+ id : log . id ,
403
+ level : log . level || "info" ,
404
+ output : log . output ,
405
+ time : log . created_at ,
406
+ } ) ) ;
407
+
408
+ if ( ! previousLogs ) {
409
+ return newLogs ;
410
+ }
411
+
412
+ return [ ...previousLogs , ...newLogs ] ;
413
+ } ) ;
414
+ } ,
415
+ onError : ( ) => {
416
+ displayError ( "Error on getting agent logs" ) ;
417
+ } ,
418
+ } ) ;
419
+
420
+ return ( ) => {
421
+ socket . current ?. close ( ) ;
422
+ } ;
423
+ } , [ agentId , enabled ] ) ;
424
+
425
+ return logs ;
426
+ } ;
427
+
399
428
const useStyles = makeStyles ( ( theme ) => ( {
400
429
agentRow : {
401
430
backgroundColor : theme . palette . background . paperLight ,
0 commit comments