@@ -7,6 +7,7 @@ import IconButton from "@mui/material/IconButton";
7
7
import Tooltip from "@mui/material/Tooltip" ;
8
8
import { health , refreshHealth } from "api/queries/debug" ;
9
9
import type { HealthSeverity } from "api/typesGenerated" ;
10
+ import { ErrorAlert } from "components/Alert/ErrorAlert" ;
10
11
import { Loader } from "components/Loader/Loader" ;
11
12
import { type ClassName , useClassName } from "hooks/useClassName" ;
12
13
import kebabCase from "lodash/fp/kebabCase" ;
@@ -22,7 +23,11 @@ import { HealthIcon } from "./Content";
22
23
export const HealthLayout : FC = ( ) => {
23
24
const theme = useTheme ( ) ;
24
25
const queryClient = useQueryClient ( ) ;
25
- const { data : healthStatus } = useQuery ( {
26
+ const {
27
+ data : healthStatus ,
28
+ isLoading,
29
+ error,
30
+ } = useQuery ( {
26
31
...health ( ) ,
27
32
refetchInterval : 30_000 ,
28
33
} ) ;
@@ -42,161 +47,171 @@ export const HealthLayout: FC = () => {
42
47
const link = useClassName ( classNames . link , [ ] ) ;
43
48
const activeLink = useClassName ( classNames . activeLink , [ ] ) ;
44
49
50
+ if ( isLoading || ! healthStatus ) {
51
+ return (
52
+ < div className = "p-6" >
53
+ < Loader />
54
+ </ div >
55
+ ) ;
56
+ }
57
+
58
+ if ( error ) {
59
+ return (
60
+ < div className = "p-6" >
61
+ < ErrorAlert error = { error } />
62
+ </ div >
63
+ ) ;
64
+ }
65
+
45
66
return (
46
67
< >
47
68
< Helmet >
48
69
< title > { pageTitle ( "Health" ) } </ title >
49
70
</ Helmet >
50
71
51
- { healthStatus ? (
52
- < DashboardFullPage >
72
+ < DashboardFullPage >
73
+ < div
74
+ css = { {
75
+ display : "flex" ,
76
+ flexBasis : 0 ,
77
+ flex : 1 ,
78
+ overflow : "hidden" ,
79
+ } }
80
+ >
53
81
< div
54
82
css = { {
55
- display : "flex" ,
56
- flexBasis : 0 ,
57
- flex : 1 ,
58
- overflow : "hidden" ,
83
+ width : 256 ,
84
+ flexShrink : 0 ,
85
+ borderRight : `1px solid ${ theme . palette . divider } ` ,
86
+ fontSize : 14 ,
59
87
} }
60
88
>
61
89
< div
62
90
css = { {
63
- width : 256 ,
64
- flexShrink : 0 ,
65
- borderRight : `1px solid ${ theme . palette . divider } ` ,
66
- fontSize : 14 ,
91
+ padding : 24 ,
92
+ display : "flex" ,
93
+ flexDirection : "column" ,
94
+ gap : 16 ,
67
95
} }
68
96
>
69
- < div
70
- css = { {
71
- padding : 24 ,
72
- display : "flex" ,
73
- flexDirection : "column" ,
74
- gap : 16 ,
75
- } }
76
- >
77
- < div >
78
- < div
79
- css = { {
80
- display : "flex" ,
81
- alignItems : "center" ,
82
- justifyContent : "space-between" ,
83
- } }
84
- >
85
- < HealthIcon size = { 32 } severity = { healthStatus . severity } />
86
-
87
- < Tooltip title = "Refresh health checks" >
88
- < IconButton
89
- size = "small"
90
- disabled = { isRefreshing }
91
- data-testid = "healthcheck-refresh-button"
92
- onClick = { ( ) => {
93
- forceRefresh ( ) ;
94
- } }
95
- >
96
- { isRefreshing ? (
97
- < CircularProgress size = { 16 } />
98
- ) : (
99
- < ReplayIcon css = { { width : 20 , height : 20 } } />
100
- ) }
101
- </ IconButton >
102
- </ Tooltip >
103
- </ div >
104
- < div css = { { fontWeight : 500 , marginTop : 16 } } >
105
- { healthStatus . healthy ? "Healthy" : "Unhealthy" }
106
- </ div >
107
- < div
108
- css = { {
109
- color : theme . palette . text . secondary ,
110
- lineHeight : "150%" ,
111
- } }
112
- >
113
- { healthStatus . healthy
114
- ? Object . keys ( visibleSections ) . some ( ( key ) => {
115
- const section =
116
- healthStatus [ key as keyof typeof visibleSections ] ;
117
- return (
118
- section . warnings && section . warnings . length > 0
119
- ) ;
120
- } )
121
- ? "All systems operational, but performance might be degraded"
122
- : "All systems operational"
123
- : "Some issues have been detected" }
124
- </ div >
125
- </ div >
97
+ < div >
98
+ < div
99
+ css = { {
100
+ display : "flex" ,
101
+ alignItems : "center" ,
102
+ justifyContent : "space-between" ,
103
+ } }
104
+ >
105
+ < HealthIcon size = { 32 } severity = { healthStatus . severity } />
126
106
127
- < div css = { { display : "flex" , flexDirection : "column" } } >
128
- < span css = { { fontWeight : 500 } } > Last check</ span >
129
- < span
130
- data-chromatic = "ignore"
131
- css = { {
132
- color : theme . palette . text . secondary ,
133
- lineHeight : "150%" ,
134
- } }
135
- >
136
- { createDayString ( healthStatus . time ) }
137
- </ span >
107
+ < Tooltip title = "Refresh health checks" >
108
+ < IconButton
109
+ size = "small"
110
+ disabled = { isRefreshing }
111
+ data-testid = "healthcheck-refresh-button"
112
+ onClick = { ( ) => {
113
+ forceRefresh ( ) ;
114
+ } }
115
+ >
116
+ { isRefreshing ? (
117
+ < CircularProgress size = { 16 } />
118
+ ) : (
119
+ < ReplayIcon css = { { width : 20 , height : 20 } } />
120
+ ) }
121
+ </ IconButton >
122
+ </ Tooltip >
138
123
</ div >
139
-
140
- < div css = { { display : "flex" , flexDirection : "column" } } >
141
- < span css = { { fontWeight : 500 } } > Version</ span >
142
- < span
143
- data-chromatic = "ignore"
144
- css = { {
145
- color : theme . palette . text . secondary ,
146
- lineHeight : "150%" ,
147
- } }
148
- >
149
- { healthStatus . coder_version }
150
- </ span >
124
+ < div css = { { fontWeight : 500 , marginTop : 16 } } >
125
+ { healthStatus . healthy ? "Healthy" : "Unhealthy" }
126
+ </ div >
127
+ < div
128
+ css = { {
129
+ color : theme . palette . text . secondary ,
130
+ lineHeight : "150%" ,
131
+ } }
132
+ >
133
+ { healthStatus . healthy
134
+ ? Object . keys ( visibleSections ) . some ( ( key ) => {
135
+ const section =
136
+ healthStatus [ key as keyof typeof visibleSections ] ;
137
+ return section . warnings && section . warnings . length > 0 ;
138
+ } )
139
+ ? "All systems operational, but performance might be degraded"
140
+ : "All systems operational"
141
+ : "Some issues have been detected" }
151
142
</ div >
152
143
</ div >
153
144
154
- < nav css = { { display : "flex" , flexDirection : "column" , gap : 1 } } >
155
- { Object . entries ( visibleSections )
156
- . sort ( )
157
- . map ( ( [ key , label ] ) => {
158
- const healthSection =
159
- healthStatus [ key as keyof typeof visibleSections ] ;
160
-
161
- return (
162
- < NavLink
163
- end
164
- key = { key }
165
- to = { `/health/${ kebabCase ( key ) } ` }
166
- className = { ( { isActive } ) =>
167
- cx ( [ link , isActive && activeLink ] )
168
- }
169
- >
170
- < HealthIcon
171
- size = { 16 }
172
- severity = { healthSection . severity as HealthSeverity }
173
- />
174
- { label }
175
- { healthSection . dismissed && (
176
- < NotificationsOffOutlined
177
- css = { {
178
- fontSize : 14 ,
179
- marginLeft : "auto" ,
180
- color : theme . palette . text . disabled ,
181
- } }
182
- />
183
- ) }
184
- </ NavLink >
185
- ) ;
186
- } ) }
187
- </ nav >
188
- </ div >
145
+ < div css = { { display : "flex" , flexDirection : "column" } } >
146
+ < span css = { { fontWeight : 500 } } > Last check</ span >
147
+ < span
148
+ data-chromatic = "ignore"
149
+ css = { {
150
+ color : theme . palette . text . secondary ,
151
+ lineHeight : "150%" ,
152
+ } }
153
+ >
154
+ { createDayString ( healthStatus . time ) }
155
+ </ span >
156
+ </ div >
189
157
190
- < div css = { { overflowY : "auto" , width : "100%" } } >
191
- < Suspense fallback = { < Loader /> } >
192
- < Outlet context = { healthStatus } />
193
- </ Suspense >
158
+ < div css = { { display : "flex" , flexDirection : "column" } } >
159
+ < span css = { { fontWeight : 500 } } > Version</ span >
160
+ < span
161
+ data-chromatic = "ignore"
162
+ css = { {
163
+ color : theme . palette . text . secondary ,
164
+ lineHeight : "150%" ,
165
+ } }
166
+ >
167
+ { healthStatus . coder_version }
168
+ </ span >
169
+ </ div >
194
170
</ div >
171
+
172
+ < nav css = { { display : "flex" , flexDirection : "column" , gap : 1 } } >
173
+ { Object . entries ( visibleSections )
174
+ . sort ( )
175
+ . map ( ( [ key , label ] ) => {
176
+ const healthSection =
177
+ healthStatus [ key as keyof typeof visibleSections ] ;
178
+
179
+ return (
180
+ < NavLink
181
+ end
182
+ key = { key }
183
+ to = { `/health/${ kebabCase ( key ) } ` }
184
+ className = { ( { isActive } ) =>
185
+ cx ( [ link , isActive && activeLink ] )
186
+ }
187
+ >
188
+ < HealthIcon
189
+ size = { 16 }
190
+ severity = { healthSection . severity as HealthSeverity }
191
+ />
192
+ { label }
193
+ { healthSection . dismissed && (
194
+ < NotificationsOffOutlined
195
+ css = { {
196
+ fontSize : 14 ,
197
+ marginLeft : "auto" ,
198
+ color : theme . palette . text . disabled ,
199
+ } }
200
+ />
201
+ ) }
202
+ </ NavLink >
203
+ ) ;
204
+ } ) }
205
+ </ nav >
195
206
</ div >
196
- </ DashboardFullPage >
197
- ) : (
198
- < Loader />
199
- ) }
207
+
208
+ < div css = { { overflowY : "auto" , width : "100%" } } >
209
+ < Suspense fallback = { < Loader /> } >
210
+ < Outlet context = { healthStatus } />
211
+ </ Suspense >
212
+ </ div >
213
+ </ div >
214
+ </ DashboardFullPage >
200
215
</ >
201
216
) ;
202
217
} ;
0 commit comments