@@ -5,13 +5,25 @@ import ListItem from "@mui/material/ListItem"
5
5
import { makeStyles } 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"
8
+ import { FC , useRef , useState } from "react"
9
9
import { NavLink , useLocation } 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 SignalCellular1BarOutlined from "@mui/icons-material/SignalCellular1BarOutlined"
23
+ import SignalCellular2BarOutlined from "@mui/icons-material/SignalCellular2BarOutlined"
24
+ import SignalCellular4BarOutlined from "@mui/icons-material/SignalCellular4BarOutlined"
25
+ import SignalCellularConnectedNoInternet0BarOutlined from "@mui/icons-material/SignalCellularConnectedNoInternet0BarOutlined"
26
+ import { SvgIconProps } from "@mui/material/SvgIcon"
15
27
16
28
export const USERS_LINK = `/users?filter=${ encodeURIComponent ( "status:active" ) } `
17
29
@@ -23,6 +35,7 @@ export interface NavbarViewProps {
23
35
onSignOut : ( ) => void
24
36
canViewAuditLog : boolean
25
37
canViewDeployment : boolean
38
+ proxyContextValue : ProxyContextValue
26
39
}
27
40
28
41
export const Language = {
@@ -83,14 +96,15 @@ const NavItems: React.FC<
83
96
</ List >
84
97
)
85
98
}
86
- export const NavbarView : React . FC < React . PropsWithChildren < NavbarViewProps > > = ( {
99
+ export const NavbarView : FC < NavbarViewProps > = ( {
87
100
user,
88
101
logo_url,
89
102
buildInfo,
90
103
supportLinks,
91
104
onSignOut,
92
105
canViewAuditLog,
93
106
canViewDeployment,
107
+ proxyContextValue,
94
108
} ) => {
95
109
const styles = useStyles ( )
96
110
const [ isDrawerOpen , setIsDrawerOpen ] = useState ( false )
@@ -145,7 +159,14 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
145
159
canViewDeployment = { canViewDeployment }
146
160
/>
147
161
148
- < div className = { styles . profileButton } >
162
+ < Box
163
+ display = "flex"
164
+ marginLeft = { { lg : "auto" } }
165
+ gap = { 2 }
166
+ alignItems = "center"
167
+ paddingRight = { 2 }
168
+ >
169
+ < ProxyMenu proxyContextValue = { proxyContextValue } />
149
170
{ user && (
150
171
< UserDropdown
151
172
user = { user }
@@ -154,12 +175,148 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
154
175
onSignOut = { onSignOut }
155
176
/>
156
177
) }
157
- </ div >
178
+ </ Box >
158
179
</ div >
159
180
</ nav >
160
181
)
161
182
}
162
183
184
+ const ProxyMenu : FC < { proxyContextValue : ProxyContextValue } > = ( {
185
+ proxyContextValue,
186
+ } ) => {
187
+ const buttonRef = useRef < HTMLButtonElement > ( null )
188
+ const [ isOpen , setIsOpen ] = useState ( false )
189
+ const selectedProxy = proxyContextValue . proxy . selectedProxy
190
+
191
+ const closeMenu = ( ) => setIsOpen ( false )
192
+
193
+ return (
194
+ < >
195
+ < Button
196
+ ref = { buttonRef }
197
+ onClick = { ( ) => setIsOpen ( true ) }
198
+ size = "small"
199
+ endIcon = { < KeyboardArrowDownOutlined /> }
200
+ sx = { {
201
+ "& .MuiSvgIcon-root" : { fontSize : 14 } ,
202
+ } }
203
+ >
204
+ { selectedProxy ? (
205
+ < Box display = "flex" gap = { 1 } alignItems = "center" >
206
+ < Box width = { 14 } height = { 14 } lineHeight = { 0 } >
207
+ < Box
208
+ component = "img"
209
+ src = { selectedProxy . icon_url }
210
+ alt = ""
211
+ sx = { { objectFit : "contain" } }
212
+ width = "100%"
213
+ height = "100%"
214
+ />
215
+ </ Box >
216
+ { selectedProxy . display_name }
217
+ < ProxyStatusIcon
218
+ proxy = { selectedProxy }
219
+ latency = {
220
+ proxyContextValue . proxyLatencies ?. [ selectedProxy . id ]
221
+ ?. latencyMS ?? 0
222
+ }
223
+ />
224
+ </ Box >
225
+ ) : (
226
+ "Select Proxy"
227
+ ) }
228
+ </ Button >
229
+ < Menu
230
+ open = { isOpen }
231
+ anchorEl = { buttonRef . current }
232
+ onClick = { closeMenu }
233
+ onClose = { closeMenu }
234
+ >
235
+ { proxyContextValue . proxies ?. map ( ( proxy ) => (
236
+ < MenuItem
237
+ onClick = { ( ) => {
238
+ if ( ! proxy . healthy ) {
239
+ displayError ( "Please select a healthy workspace proxy." )
240
+ closeMenu ( )
241
+ return
242
+ }
243
+
244
+ proxyContextValue . setProxy ( proxy )
245
+ closeMenu ( )
246
+ } }
247
+ key = { proxy . id }
248
+ selected = { proxy . id === proxyContextValue . proxy . selectedProxy ?. id }
249
+ sx = { {
250
+ "& .MuiSvgIcon-root" : { fontSize : 16 } ,
251
+ } }
252
+ >
253
+ < Box display = "flex" gap = { 2 } alignItems = "center" width = "100%" >
254
+ < Box width = { 16 } height = { 16 } lineHeight = { 0 } >
255
+ < Box
256
+ component = "img"
257
+ src = { proxy . icon_url }
258
+ alt = ""
259
+ sx = { { objectFit : "contain" } }
260
+ width = "100%"
261
+ height = "100%"
262
+ />
263
+ </ Box >
264
+ { proxy . display_name }
265
+ < ProxyStatusIcon
266
+ proxy = { proxy }
267
+ latency = {
268
+ proxyContextValue . proxyLatencies ?. [ proxy . id ] ?. latencyMS ?? 0
269
+ }
270
+ sx = { {
271
+ marginLeft : "auto" ,
272
+ } }
273
+ />
274
+ </ Box >
275
+ </ MenuItem >
276
+ ) ) }
277
+ </ Menu >
278
+ </ >
279
+ )
280
+ }
281
+
282
+ const ProxyStatusIcon : FC <
283
+ { proxy : TypesGen . Region ; latency : number } & SvgIconProps
284
+ > = ( { proxy, latency, ...svgProps } ) => {
285
+ if ( ! proxy . healthy ) {
286
+ return (
287
+ < SignalCellularConnectedNoInternet0BarOutlined
288
+ { ...svgProps }
289
+ sx = { { color : ( theme ) => theme . palette . warning . light , ...svgProps . sx } }
290
+ />
291
+ )
292
+ }
293
+
294
+ if ( latency >= 150 && latency < 300 ) {
295
+ return (
296
+ < SignalCellular2BarOutlined
297
+ { ...svgProps }
298
+ sx = { { color : ( theme ) => theme . palette . warning . light , ...svgProps . sx } }
299
+ />
300
+ )
301
+ }
302
+
303
+ if ( latency >= 300 ) {
304
+ return (
305
+ < SignalCellular1BarOutlined
306
+ { ...svgProps }
307
+ sx = { { color : ( theme ) => theme . palette . error . light , ...svgProps . sx } }
308
+ />
309
+ )
310
+ }
311
+
312
+ return (
313
+ < SignalCellular4BarOutlined
314
+ { ...svgProps }
315
+ sx = { { color : ( theme ) => theme . palette . success . light , ...svgProps . sx } }
316
+ />
317
+ )
318
+ }
319
+
163
320
const useStyles = makeStyles ( ( theme ) => ( {
164
321
root : {
165
322
height : navHeight ,
@@ -192,12 +349,6 @@ const useStyles = makeStyles((theme) => ({
192
349
display : "flex" ,
193
350
} ,
194
351
} ,
195
- profileButton : {
196
- paddingRight : theme . spacing ( 2 ) ,
197
- [ theme . breakpoints . up ( "md" ) ] : {
198
- marginLeft : "auto" ,
199
- } ,
200
- } ,
201
352
mobileMenuButton : {
202
353
[ theme . breakpoints . up ( "md" ) ] : {
203
354
display : "none" ,
0 commit comments