@@ -7,6 +7,7 @@ import IconButton from "@mui/material/IconButton";
77import Tooltip from "@mui/material/Tooltip" ;
88import { health , refreshHealth } from "api/queries/debug" ;
99import type { HealthSeverity } from "api/typesGenerated" ;
10+ import { ErrorAlert } from "components/Alert/ErrorAlert" ;
1011import { Loader } from "components/Loader/Loader" ;
1112import { type ClassName , useClassName } from "hooks/useClassName" ;
1213import kebabCase from "lodash/fp/kebabCase" ;
@@ -22,7 +23,11 @@ import { HealthIcon } from "./Content";
2223export const HealthLayout :FC = ( ) => {
2324const theme = useTheme ( ) ;
2425const queryClient = useQueryClient ( ) ;
25- const { data :healthStatus } = useQuery ( {
26+ const {
27+ data :healthStatus ,
28+ isLoading,
29+ error,
30+ } = useQuery ( {
2631...health ( ) ,
2732refetchInterval :30_000 ,
2833} ) ;
@@ -42,161 +47,171 @@ export const HealthLayout: FC = () => {
4247const link = useClassName ( classNames . link , [ ] ) ;
4348const activeLink = useClassName ( classNames . activeLink , [ ] ) ;
4449
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+
4566return (
4667< >
4768< Helmet >
4869< title > { pageTitle ( "Health" ) } </ title >
4970</ Helmet >
5071
51- { healthStatus ?(
52- < DashboardFullPage >
72+ < DashboardFullPage >
73+ < div
74+ css = { {
75+ display :"flex" ,
76+ flexBasis :0 ,
77+ flex :1 ,
78+ overflow :"hidden" ,
79+ } }
80+ >
5381< div
5482css = { {
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 ,
5987} }
6088>
6189< div
6290css = { {
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 ,
6795} }
6896>
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 } />
126106
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 >
138123</ 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" }
151142</ div >
152143</ div >
153144
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 >
189157
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 >
194170</ 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 >
195206</ 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 >
200215</ >
201216) ;
202217} ;