Skip to content

Commit 925cb45

Browse files
BrunoQuaresmaammario
authored andcommitted
feat(site): Add proxy menu into navbar (coder#7715)
1 parent 70658d4 commit 925cb45

File tree

10 files changed

+251
-37
lines changed

10 files changed

+251
-37
lines changed

site/src/components/Navbar/Navbar.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { render, screen, waitFor } from "@testing-library/react"
22
import { App } from "app"
3-
import { Language } from "components/NavbarView/NavbarView"
3+
import { Language } from "./NavbarView"
44
import { rest } from "msw"
55
import {
66
MockEntitlementsWithAuditLog,

site/src/components/Navbar/Navbar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { useFeatureVisibility } from "hooks/useFeatureVisibility"
44
import { useMe } from "hooks/useMe"
55
import { usePermissions } from "hooks/usePermissions"
66
import { FC } from "react"
7-
import { NavbarView } from "../NavbarView/NavbarView"
7+
import { NavbarView } from "./NavbarView"
8+
import { useProxy } from "contexts/ProxyContext"
89

910
export const Navbar: FC = () => {
1011
const { appearance, buildInfo } = useDashboard()
@@ -16,6 +17,8 @@ export const Navbar: FC = () => {
1617
featureVisibility["audit_log"] && Boolean(permissions.viewAuditLog)
1718
const canViewDeployment = Boolean(permissions.viewDeploymentValues)
1819
const onSignOut = () => authSend("SIGN_OUT")
20+
const proxyContextValue = useProxy()
21+
const dashboard = useDashboard()
1922

2023
return (
2124
<NavbarView
@@ -26,6 +29,9 @@ export const Navbar: FC = () => {
2629
onSignOut={onSignOut}
2730
canViewAuditLog={canViewAuditLog}
2831
canViewDeployment={canViewDeployment}
32+
proxyContextValue={
33+
dashboard.experiments.includes("moons") ? proxyContextValue : undefined
34+
}
2935
/>
3036
)
3137
}

site/src/components/NavbarView/NavbarView.test.tsx renamed to site/src/components/Navbar/NavbarView.test.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
import { screen } from "@testing-library/react"
2-
import { MockUser, MockUser2 } from "../../testHelpers/entities"
2+
import {
3+
MockPrimaryWorkspaceProxy,
4+
MockUser,
5+
MockUser2,
6+
} from "../../testHelpers/entities"
37
import { render } from "../../testHelpers/renderHelpers"
48
import { Language as navLanguage, NavbarView } from "./NavbarView"
9+
import { ProxyContextValue } from "contexts/ProxyContext"
10+
import { action } from "@storybook/addon-actions"
11+
12+
const proxyContextValue: ProxyContextValue = {
13+
proxy: {
14+
preferredPathAppURL: "",
15+
preferredWildcardHostname: "",
16+
proxy: MockPrimaryWorkspaceProxy,
17+
},
18+
isLoading: false,
19+
isFetched: true,
20+
setProxy: jest.fn(),
21+
clearProxy: action("clearProxy"),
22+
proxyLatencies: {},
23+
}
524

625
describe("NavbarView", () => {
726
const noop = () => {
@@ -23,6 +42,7 @@ describe("NavbarView", () => {
2342
it("workspaces nav link has the correct href", async () => {
2443
render(
2544
<NavbarView
45+
proxyContextValue={proxyContextValue}
2646
user={MockUser}
2747
onSignOut={noop}
2848
canViewAuditLog
@@ -36,6 +56,7 @@ describe("NavbarView", () => {
3656
it("templates nav link has the correct href", async () => {
3757
render(
3858
<NavbarView
59+
proxyContextValue={proxyContextValue}
3960
user={MockUser}
4061
onSignOut={noop}
4162
canViewAuditLog
@@ -49,6 +70,7 @@ describe("NavbarView", () => {
4970
it("users nav link has the correct href", async () => {
5071
render(
5172
<NavbarView
73+
proxyContextValue={proxyContextValue}
5274
user={MockUser}
5375
onSignOut={noop}
5476
canViewAuditLog
@@ -70,6 +92,7 @@ describe("NavbarView", () => {
7092
// When
7193
render(
7294
<NavbarView
95+
proxyContextValue={proxyContextValue}
7396
user={mockUser}
7497
onSignOut={noop}
7598
canViewAuditLog
@@ -86,6 +109,7 @@ describe("NavbarView", () => {
86109
it("audit nav link has the correct href", async () => {
87110
render(
88111
<NavbarView
112+
proxyContextValue={proxyContextValue}
89113
user={MockUser}
90114
onSignOut={noop}
91115
canViewAuditLog
@@ -99,6 +123,7 @@ describe("NavbarView", () => {
99123
it("audit nav link is hidden for members", async () => {
100124
render(
101125
<NavbarView
126+
proxyContextValue={proxyContextValue}
102127
user={MockUser2}
103128
onSignOut={noop}
104129
canViewAuditLog={false}
@@ -112,6 +137,7 @@ describe("NavbarView", () => {
112137
it("deployment nav link has the correct href", async () => {
113138
render(
114139
<NavbarView
140+
proxyContextValue={proxyContextValue}
115141
user={MockUser}
116142
onSignOut={noop}
117143
canViewAuditLog
@@ -127,6 +153,7 @@ describe("NavbarView", () => {
127153
it("deployment nav link is hidden for members", async () => {
128154
render(
129155
<NavbarView
156+
proxyContextValue={proxyContextValue}
130157
user={MockUser2}
131158
onSignOut={noop}
132159
canViewAuditLog={false}

site/src/components/NavbarView/NavbarView.tsx renamed to site/src/components/Navbar/NavbarView.tsx

Lines changed: 179 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,27 @@ import Drawer from "@mui/material/Drawer"
22
import IconButton from "@mui/material/IconButton"
33
import List from "@mui/material/List"
44
import ListItem from "@mui/material/ListItem"
5-
import { makeStyles } from "@mui/styles"
5+
import { makeStyles, useTheme } from "@mui/styles"
66
import MenuIcon from "@mui/icons-material/Menu"
77
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"
1010
import { colors } from "theme/colors"
1111
import * as TypesGen from "../../api/typesGenerated"
1212
import { navHeight } from "../../theme/constants"
1313
import { combineClasses } from "../../utils/combineClasses"
1414
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"
1526

1627
export const USERS_LINK = `/users?filter=${encodeURIComponent("status:active")}`
1728

@@ -23,6 +34,7 @@ export interface NavbarViewProps {
2334
onSignOut: () => void
2435
canViewAuditLog: boolean
2536
canViewDeployment: boolean
37+
proxyContextValue?: ProxyContextValue
2638
}
2739

2840
export const Language = {
@@ -83,14 +95,15 @@ const NavItems: React.FC<
8395
</List>
8496
)
8597
}
86-
export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
98+
export const NavbarView: FC<NavbarViewProps> = ({
8799
user,
88100
logo_url,
89101
buildInfo,
90102
supportLinks,
91103
onSignOut,
92104
canViewAuditLog,
93105
canViewDeployment,
106+
proxyContextValue,
94107
}) => {
95108
const styles = useStyles()
96109
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
@@ -145,7 +158,16 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
145158
canViewDeployment={canViewDeployment}
146159
/>
147160

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+
)}
149171
{user && (
150172
<UserDropdown
151173
user={user}
@@ -154,12 +176,163 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
154176
onSignOut={onSignOut}
155177
/>
156178
)}
157-
</div>
179+
</Box>
158180
</div>
159181
</nav>
160182
)
161183
}
162184

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+
163336
const useStyles = makeStyles((theme) => ({
164337
root: {
165338
height: navHeight,
@@ -192,12 +365,6 @@ const useStyles = makeStyles((theme) => ({
192365
display: "flex",
193366
},
194367
},
195-
profileButton: {
196-
paddingRight: theme.spacing(2),
197-
[theme.breakpoints.up("md")]: {
198-
marginLeft: "auto",
199-
},
200-
},
201368
mobileMenuButton: {
202369
[theme.breakpoints.up("md")]: {
203370
display: "none",

site/src/components/UsersLayout/UsersLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { makeStyles } from "@mui/styles"
44
import GroupAdd from "@mui/icons-material/GroupAddOutlined"
55
import PersonAdd from "@mui/icons-material/PersonAddOutlined"
66
import { useMachine } from "@xstate/react"
7-
import { USERS_LINK } from "components/NavbarView/NavbarView"
7+
import { USERS_LINK } from "components/Navbar/NavbarView"
88
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"
99
import { useFeatureVisibility } from "hooks/useFeatureVisibility"
1010
import { usePermissions } from "hooks/usePermissions"

site/src/contexts/ProxyContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
127127
return (
128128
<ProxyContext.Provider
129129
value={{
130+
proxyLatencies,
130131
userProxy: userSavedProxy,
131-
proxyLatencies: proxyLatencies,
132132
proxy: experimentEnabled
133133
? proxy
134134
: {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy