@@ -2,16 +2,27 @@ import Drawer from "@mui/material/Drawer"
2
2
import IconButton from "@mui/material/IconButton"
3
3
import List from "@mui/material/List"
4
4
import ListItem from "@mui/material/ListItem"
5
- import { makeStyles } from "@mui/styles"
5
+ import { makeStyles , useTheme } from "@mui/styles"
6
6
import MenuIcon from "@mui/icons-material/Menu"
7
7
import { CoderIcon } from "components/Icons/CoderIcon"
8
- import { useState } from "react"
9
- import { NavLink , useLocation } from "react-router-dom"
8
+ import { FC , useRef , useState } from "react"
9
+ import { NavLink , useLocation , useNavigate } from "react-router-dom"
10
10
import { colors } from "theme/colors"
11
11
import * as TypesGen from "../../api/typesGenerated"
12
12
import { navHeight } from "../../theme/constants"
13
13
import { combineClasses } from "../../utils/combineClasses"
14
14
import { UserDropdown } from "../UserDropdown/UsersDropdown"
15
+ import Box from "@mui/material/Box"
16
+ import Menu from "@mui/material/Menu"
17
+ import Button from "@mui/material/Button"
18
+ import MenuItem from "@mui/material/MenuItem"
19
+ import KeyboardArrowDownOutlined from "@mui/icons-material/KeyboardArrowDownOutlined"
20
+ import { ProxyContextValue } from "contexts/ProxyContext"
21
+ import { displayError } from "components/GlobalSnackbar/utils"
22
+ import Divider from "@mui/material/Divider"
23
+ import HelpOutline from "@mui/icons-material/HelpOutline"
24
+ import Tooltip from "@mui/material/Tooltip"
25
+ import Skeleton from "@mui/material/Skeleton"
15
26
16
27
export const USERS_LINK = `/users?filter=${ encodeURIComponent ( "status:active" ) } `
17
28
@@ -23,6 +34,7 @@ export interface NavbarViewProps {
23
34
onSignOut : ( ) => void
24
35
canViewAuditLog : boolean
25
36
canViewDeployment : boolean
37
+ proxyContextValue ?: ProxyContextValue
26
38
}
27
39
28
40
export const Language = {
@@ -83,14 +95,15 @@ const NavItems: React.FC<
83
95
</ List >
84
96
)
85
97
}
86
- export const NavbarView : React . FC < React . PropsWithChildren < NavbarViewProps > > = ( {
98
+ export const NavbarView : FC < NavbarViewProps > = ( {
87
99
user,
88
100
logo_url,
89
101
buildInfo,
90
102
supportLinks,
91
103
onSignOut,
92
104
canViewAuditLog,
93
105
canViewDeployment,
106
+ proxyContextValue,
94
107
} ) => {
95
108
const styles = useStyles ( )
96
109
const [ isDrawerOpen , setIsDrawerOpen ] = useState ( false )
@@ -145,7 +158,16 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
145
158
canViewDeployment = { canViewDeployment }
146
159
/>
147
160
148
- < div className = { styles . profileButton } >
161
+ < Box
162
+ display = "flex"
163
+ marginLeft = { { lg : "auto" } }
164
+ gap = { 2 }
165
+ alignItems = "center"
166
+ paddingRight = { 2 }
167
+ >
168
+ { proxyContextValue && (
169
+ < ProxyMenu proxyContextValue = { proxyContextValue } />
170
+ ) }
149
171
{ user && (
150
172
< UserDropdown
151
173
user = { user }
@@ -154,12 +176,163 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
154
176
onSignOut = { onSignOut }
155
177
/>
156
178
) }
157
- </ div >
179
+ </ Box >
158
180
</ div >
159
181
</ nav >
160
182
)
161
183
}
162
184
185
+ const ProxyMenu : FC < { proxyContextValue : ProxyContextValue } > = ( {
186
+ proxyContextValue,
187
+ } ) => {
188
+ const buttonRef = useRef < HTMLButtonElement > ( null )
189
+ const [ isOpen , setIsOpen ] = useState ( false )
190
+ const selectedProxy = proxyContextValue . proxy . proxy
191
+ const closeMenu = ( ) => setIsOpen ( false )
192
+ const navigate = useNavigate ( )
193
+
194
+ if ( ! proxyContextValue . isFetched ) {
195
+ return (
196
+ < Skeleton
197
+ width = "160px"
198
+ height = { 30 }
199
+ sx = { { borderRadius : "4px" , transform : "none" } }
200
+ />
201
+ )
202
+ }
203
+
204
+ return (
205
+ < >
206
+ < Button
207
+ ref = { buttonRef }
208
+ onClick = { ( ) => setIsOpen ( true ) }
209
+ size = "small"
210
+ endIcon = { < KeyboardArrowDownOutlined /> }
211
+ sx = { {
212
+ borderRadius : "4px" ,
213
+ "& .MuiSvgIcon-root" : { fontSize : 14 } ,
214
+ } }
215
+ >
216
+ { selectedProxy ? (
217
+ < Box display = "flex" gap = { 2 } alignItems = "center" >
218
+ < Box width = { 14 } height = { 14 } lineHeight = { 0 } >
219
+ < Box
220
+ component = "img"
221
+ src = { selectedProxy . icon_url }
222
+ alt = ""
223
+ sx = { { objectFit : "contain" } }
224
+ width = "100%"
225
+ height = "100%"
226
+ />
227
+ </ Box >
228
+ { selectedProxy . display_name }
229
+ < ProxyStatusLatency
230
+ proxy = { selectedProxy }
231
+ latency = {
232
+ proxyContextValue . proxyLatencies ?. [ selectedProxy . id ] ?. latencyMS
233
+ }
234
+ />
235
+ </ Box >
236
+ ) : (
237
+ "Select Proxy"
238
+ ) }
239
+ </ Button >
240
+ < Menu
241
+ open = { isOpen }
242
+ anchorEl = { buttonRef . current }
243
+ onClick = { closeMenu }
244
+ onClose = { closeMenu }
245
+ sx = { { "& .MuiMenu-paper" : { py : 1 } } }
246
+ >
247
+ { proxyContextValue . proxies ?. map ( ( proxy ) => (
248
+ < MenuItem
249
+ onClick = { ( ) => {
250
+ if ( ! proxy . healthy ) {
251
+ displayError ( "Please select a healthy workspace proxy." )
252
+ closeMenu ( )
253
+ return
254
+ }
255
+
256
+ proxyContextValue . setProxy ( proxy )
257
+ closeMenu ( )
258
+ } }
259
+ key = { proxy . id }
260
+ selected = { proxy . id === selectedProxy ?. id }
261
+ sx = { {
262
+ fontSize : 14 ,
263
+ } }
264
+ >
265
+ < Box display = "flex" gap = { 3 } alignItems = "center" width = "100%" >
266
+ < Box width = { 14 } height = { 14 } lineHeight = { 0 } >
267
+ < Box
268
+ component = "img"
269
+ src = { proxy . icon_url }
270
+ alt = ""
271
+ sx = { { objectFit : "contain" } }
272
+ width = "100%"
273
+ height = "100%"
274
+ />
275
+ </ Box >
276
+ { proxy . display_name }
277
+ < ProxyStatusLatency
278
+ proxy = { proxy }
279
+ latency = {
280
+ proxyContextValue . proxyLatencies ?. [ proxy . id ] ?. latencyMS
281
+ }
282
+ />
283
+ </ Box >
284
+ </ MenuItem >
285
+ ) ) }
286
+ < Divider sx = { { borderColor : ( theme ) => theme . palette . divider } } />
287
+ < MenuItem
288
+ sx = { { fontSize : 14 } }
289
+ onClick = { ( ) => {
290
+ navigate ( "/settings/workspace-proxies" )
291
+ } }
292
+ >
293
+ Proxy settings
294
+ </ MenuItem >
295
+ </ Menu >
296
+ </ >
297
+ )
298
+ }
299
+
300
+ const ProxyStatusLatency : FC < { proxy : TypesGen . Region ; latency ?: number } > = ( {
301
+ proxy,
302
+ latency,
303
+ } ) => {
304
+ const theme = useTheme ( )
305
+ let color = theme . palette . success . light
306
+
307
+ if ( ! latency ) {
308
+ return (
309
+ < Tooltip title = "Latency not available" >
310
+ < HelpOutline
311
+ sx = { {
312
+ ml : "auto" ,
313
+ fontSize : "14px !important" ,
314
+ color : ( theme ) => theme . palette . text . secondary ,
315
+ } }
316
+ />
317
+ </ Tooltip >
318
+ )
319
+ }
320
+
321
+ if ( latency >= 300 ) {
322
+ color = theme . palette . error . light
323
+ }
324
+
325
+ if ( ! proxy . healthy || latency >= 100 ) {
326
+ color = theme . palette . warning . light
327
+ }
328
+
329
+ return (
330
+ < Box sx = { { color, fontSize : 13 , marginLeft : "auto" } } >
331
+ { latency . toFixed ( 0 ) } ms
332
+ </ Box >
333
+ )
334
+ }
335
+
163
336
const useStyles = makeStyles ( ( theme ) => ( {
164
337
root : {
165
338
height : navHeight ,
@@ -192,12 +365,6 @@ const useStyles = makeStyles((theme) => ({
192
365
display : "flex" ,
193
366
} ,
194
367
} ,
195
- profileButton : {
196
- paddingRight : theme . spacing ( 2 ) ,
197
- [ theme . breakpoints . up ( "md" ) ] : {
198
- marginLeft : "auto" ,
199
- } ,
200
- } ,
201
368
mobileMenuButton : {
202
369
[ theme . breakpoints . up ( "md" ) ] : {
203
370
display : "none" ,
0 commit comments