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,10 @@ export const AgentRow: FC<AgentRowProps> = ({
68
76
hideVSCodeDesktopButton,
69
77
serverVersion,
70
78
onUpdateAgent,
71
- storybookLogs,
72
79
storybookAgentMetadata,
73
80
sshPrefix,
74
81
} ) => {
75
82
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
83
const theme = useTheme ( ) ;
90
84
const startupScriptAnchorRef = useRef < HTMLButtonElement > ( null ) ;
91
85
const [ startupScriptOpen , setStartupScriptOpen ] = useState ( false ) ;
@@ -94,36 +88,17 @@ export const AgentRow: FC<AgentRowProps> = ({
94
88
showApps &&
95
89
( ( agent . status === "connected" && hasAppsToDisplay ) ||
96
90
agent . status === "connecting" ) ;
97
- const hasStartupFeatures =
98
- Boolean ( agent . logs_length ) || Boolean ( logsMachine . context . logs ?. length ) ;
91
+ const hasStartupFeatures = Boolean ( agent . logs_length ) ;
99
92
const { proxy } = useProxy ( ) ;
100
-
101
93
const [ showLogs , setShowLogs ] = useState (
102
94
[ "starting" , "start_timeout" ] . includes ( agent . lifecycle_state ) &&
103
95
hasStartupFeatures ,
104
96
) ;
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 ] ) ;
97
+ const agentLogs = useAgentLogs ( agent . id , { enabled : showLogs } ) ;
123
98
const logListRef = useRef < List > ( null ) ;
124
99
const logListDivRef = useRef < HTMLDivElement > ( null ) ;
125
100
const startupLogs = useMemo ( ( ) => {
126
- const allLogs = logsMachine . context . logs || [ ] ;
101
+ const allLogs = agentLogs || [ ] ;
127
102
128
103
const logs = [ ...allLogs ] ;
129
104
if ( agent . logs_overflowed ) {
@@ -135,8 +110,13 @@ export const AgentRow: FC<AgentRowProps> = ({
135
110
} ) ;
136
111
}
137
112
return logs ;
138
- } , [ logsMachine . context . logs , agent . logs_overflowed ] ) ;
113
+ } , [ agentLogs , agent . logs_overflowed ] ) ;
139
114
const [ bottomOfLogs , setBottomOfLogs ] = useState ( true ) ;
115
+
116
+ useEffect ( ( ) => {
117
+ setShowLogs ( agent . lifecycle_state !== "ready" && hasStartupFeatures ) ;
118
+ } , [ agent . lifecycle_state , hasStartupFeatures ] ) ;
119
+
140
120
// This is a layout effect to remove flicker when we're scrolling to the bottom.
141
121
useLayoutEffect ( ( ) => {
142
122
// If we're currently watching the bottom, we always want to stay at the bottom.
@@ -396,6 +376,48 @@ export const AgentRow: FC<AgentRowProps> = ({
396
376
) ;
397
377
} ;
398
378
379
+ const useAgentLogs = ( agentId : string , { enabled } : { enabled : boolean } ) => {
380
+ const [ logs , setLogs ] = useState < LineWithID [ ] > ( ) ;
381
+ const socket = useRef < WebSocket | null > ( null ) ;
382
+
383
+ useEffect ( ( ) => {
384
+ if ( ! enabled ) {
385
+ socket . current ?. close ( ) ;
386
+ return ;
387
+ }
388
+
389
+ socket . current = API . watchWorkspaceAgentLogs ( agentId , {
390
+ // Get all logs
391
+ after : 0 ,
392
+ onMessage : ( logs ) => {
393
+ setLogs ( ( previousLogs ) => {
394
+ const newLogs : LineWithID [ ] = logs . map ( ( log ) => ( {
395
+ id : log . id ,
396
+ level : log . level || "info" ,
397
+ output : log . output ,
398
+ time : log . created_at ,
399
+ } ) ) ;
400
+
401
+ if ( ! previousLogs ) {
402
+ return newLogs ;
403
+ }
404
+
405
+ return [ ...previousLogs , ...newLogs ] ;
406
+ } ) ;
407
+ } ,
408
+ onError : ( ) => {
409
+ displayError ( "Error on gettings agent logs" ) ;
410
+ } ,
411
+ } ) ;
412
+
413
+ return ( ) => {
414
+ socket . current ?. close ( ) ;
415
+ } ;
416
+ } , [ agentId , enabled ] ) ;
417
+
418
+ return logs ;
419
+ } ;
420
+
399
421
const useStyles = makeStyles ( ( theme ) => ( {
400
422
agentRow : {
401
423
backgroundColor : theme . palette . background . paperLight ,
0 commit comments