Skip to content

Commit 07babac

Browse files
committed
Add base for proxies
1 parent e951778 commit 07babac

File tree

7 files changed

+193
-15
lines changed

7 files changed

+193
-15
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: 4 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,7 @@ 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()
1921

2022
return (
2123
<NavbarView
@@ -26,6 +28,7 @@ export const Navbar: FC = () => {
2628
onSignOut={onSignOut}
2729
canViewAuditLog={canViewAuditLog}
2830
canViewDeployment={canViewDeployment}
31+
proxyContextValue={proxyContextValue}
2932
/>
3033
)
3134
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
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+
11+
const proxyContextValue: ProxyContextValue = {
12+
proxy: {
13+
preferredPathAppURL: "",
14+
preferredWildcardHostname: "",
15+
selectedProxy: MockPrimaryWorkspaceProxy,
16+
},
17+
isLoading: false,
18+
isFetched: true,
19+
setProxy: jest.fn(),
20+
}
521

622
describe("NavbarView", () => {
723
const noop = () => {
@@ -23,6 +39,7 @@ describe("NavbarView", () => {
2339
it("workspaces nav link has the correct href", async () => {
2440
render(
2541
<NavbarView
42+
proxyContextValue={proxyContextValue}
2643
user={MockUser}
2744
onSignOut={noop}
2845
canViewAuditLog
@@ -36,6 +53,7 @@ describe("NavbarView", () => {
3653
it("templates nav link has the correct href", async () => {
3754
render(
3855
<NavbarView
56+
proxyContextValue={proxyContextValue}
3957
user={MockUser}
4058
onSignOut={noop}
4159
canViewAuditLog
@@ -49,6 +67,7 @@ describe("NavbarView", () => {
4967
it("users nav link has the correct href", async () => {
5068
render(
5169
<NavbarView
70+
proxyContextValue={proxyContextValue}
5271
user={MockUser}
5372
onSignOut={noop}
5473
canViewAuditLog
@@ -70,6 +89,7 @@ describe("NavbarView", () => {
7089
// When
7190
render(
7291
<NavbarView
92+
proxyContextValue={proxyContextValue}
7393
user={mockUser}
7494
onSignOut={noop}
7595
canViewAuditLog
@@ -86,6 +106,7 @@ describe("NavbarView", () => {
86106
it("audit nav link has the correct href", async () => {
87107
render(
88108
<NavbarView
109+
proxyContextValue={proxyContextValue}
89110
user={MockUser}
90111
onSignOut={noop}
91112
canViewAuditLog
@@ -99,6 +120,7 @@ describe("NavbarView", () => {
99120
it("audit nav link is hidden for members", async () => {
100121
render(
101122
<NavbarView
123+
proxyContextValue={proxyContextValue}
102124
user={MockUser2}
103125
onSignOut={noop}
104126
canViewAuditLog={false}
@@ -112,6 +134,7 @@ describe("NavbarView", () => {
112134
it("deployment nav link has the correct href", async () => {
113135
render(
114136
<NavbarView
137+
proxyContextValue={proxyContextValue}
115138
user={MockUser}
116139
onSignOut={noop}
117140
canViewAuditLog
@@ -127,6 +150,7 @@ describe("NavbarView", () => {
127150
it("deployment nav link is hidden for members", async () => {
128151
render(
129152
<NavbarView
153+
proxyContextValue={proxyContextValue}
130154
user={MockUser2}
131155
onSignOut={noop}
132156
canViewAuditLog={false}

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

Lines changed: 161 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,25 @@ import ListItem from "@mui/material/ListItem"
55
import { makeStyles } from "@mui/styles"
66
import MenuIcon from "@mui/icons-material/Menu"
77
import { CoderIcon } from "components/Icons/CoderIcon"
8-
import { useState } from "react"
8+
import { FC, useRef, useState } from "react"
99
import { NavLink, useLocation } 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 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"
1527

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

@@ -23,6 +35,7 @@ export interface NavbarViewProps {
2335
onSignOut: () => void
2436
canViewAuditLog: boolean
2537
canViewDeployment: boolean
38+
proxyContextValue: ProxyContextValue
2639
}
2740

2841
export const Language = {
@@ -83,14 +96,15 @@ const NavItems: React.FC<
8396
</List>
8497
)
8598
}
86-
export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
99+
export const NavbarView: FC<NavbarViewProps> = ({
87100
user,
88101
logo_url,
89102
buildInfo,
90103
supportLinks,
91104
onSignOut,
92105
canViewAuditLog,
93106
canViewDeployment,
107+
proxyContextValue,
94108
}) => {
95109
const styles = useStyles()
96110
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
@@ -145,7 +159,14 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
145159
canViewDeployment={canViewDeployment}
146160
/>
147161

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} />
149170
{user && (
150171
<UserDropdown
151172
user={user}
@@ -154,12 +175,148 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
154175
onSignOut={onSignOut}
155176
/>
156177
)}
157-
</div>
178+
</Box>
158179
</div>
159180
</nav>
160181
)
161182
}
162183

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+
163320
const useStyles = makeStyles((theme) => ({
164321
root: {
165322
height: navHeight,
@@ -192,12 +349,6 @@ const useStyles = makeStyles((theme) => ({
192349
display: "flex",
193350
},
194351
},
195-
profileButton: {
196-
paddingRight: theme.spacing(2),
197-
[theme.breakpoints.up("md")]: {
198-
marginLeft: "auto",
199-
},
200-
},
201352
mobileMenuButton: {
202353
[theme.breakpoints.up("md")]: {
203354
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
@@ -11,7 +11,7 @@ import {
1111
} from "react"
1212
import { ProxyLatencyReport, useProxyLatency } from "./useProxyLatency"
1313

14-
interface ProxyContextValue {
14+
export interface ProxyContextValue {
1515
proxy: PreferredProxy
1616
proxies?: Region[]
1717
proxyLatencies?: Record<string, ProxyLatencyReport>

0 commit comments

Comments
 (0)