feat(site): Add proxy menu into navbar (#7715)

This commit is contained in:
Bruno Quaresma
2023-05-30 11:41:23 -03:00
committed by GitHub
parent e8eb28adb4
commit c2d2751897
10 changed files with 251 additions and 37 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import { render, screen, waitFor } from "@testing-library/react"
import { App } from "app"
import { Language } from "components/NavbarView/NavbarView"
import { Language } from "./NavbarView"
import { rest } from "msw"
import {
MockEntitlementsWithAuditLog,
+7 -1
View File
@@ -4,7 +4,8 @@ import { useFeatureVisibility } from "hooks/useFeatureVisibility"
import { useMe } from "hooks/useMe"
import { usePermissions } from "hooks/usePermissions"
import { FC } from "react"
import { NavbarView } from "../NavbarView/NavbarView"
import { NavbarView } from "./NavbarView"
import { useProxy } from "contexts/ProxyContext"
export const Navbar: FC = () => {
const { appearance, buildInfo } = useDashboard()
@@ -16,6 +17,8 @@ export const Navbar: FC = () => {
featureVisibility["audit_log"] && Boolean(permissions.viewAuditLog)
const canViewDeployment = Boolean(permissions.viewDeploymentValues)
const onSignOut = () => authSend("SIGN_OUT")
const proxyContextValue = useProxy()
const dashboard = useDashboard()
return (
<NavbarView
@@ -26,6 +29,9 @@ export const Navbar: FC = () => {
onSignOut={onSignOut}
canViewAuditLog={canViewAuditLog}
canViewDeployment={canViewDeployment}
proxyContextValue={
dashboard.experiments.includes("moons") ? proxyContextValue : undefined
}
/>
)
}
@@ -1,7 +1,26 @@
import { screen } from "@testing-library/react"
import { MockUser, MockUser2 } from "../../testHelpers/entities"
import {
MockPrimaryWorkspaceProxy,
MockUser,
MockUser2,
} from "../../testHelpers/entities"
import { render } from "../../testHelpers/renderHelpers"
import { Language as navLanguage, NavbarView } from "./NavbarView"
import { ProxyContextValue } from "contexts/ProxyContext"
import { action } from "@storybook/addon-actions"
const proxyContextValue: ProxyContextValue = {
proxy: {
preferredPathAppURL: "",
preferredWildcardHostname: "",
proxy: MockPrimaryWorkspaceProxy,
},
isLoading: false,
isFetched: true,
setProxy: jest.fn(),
clearProxy: action("clearProxy"),
proxyLatencies: {},
}
describe("NavbarView", () => {
const noop = () => {
@@ -23,6 +42,7 @@ describe("NavbarView", () => {
it("workspaces nav link has the correct href", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser}
onSignOut={noop}
canViewAuditLog
@@ -36,6 +56,7 @@ describe("NavbarView", () => {
it("templates nav link has the correct href", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser}
onSignOut={noop}
canViewAuditLog
@@ -49,6 +70,7 @@ describe("NavbarView", () => {
it("users nav link has the correct href", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser}
onSignOut={noop}
canViewAuditLog
@@ -70,6 +92,7 @@ describe("NavbarView", () => {
// When
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={mockUser}
onSignOut={noop}
canViewAuditLog
@@ -86,6 +109,7 @@ describe("NavbarView", () => {
it("audit nav link has the correct href", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser}
onSignOut={noop}
canViewAuditLog
@@ -99,6 +123,7 @@ describe("NavbarView", () => {
it("audit nav link is hidden for members", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser2}
onSignOut={noop}
canViewAuditLog={false}
@@ -112,6 +137,7 @@ describe("NavbarView", () => {
it("deployment nav link has the correct href", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser}
onSignOut={noop}
canViewAuditLog
@@ -127,6 +153,7 @@ describe("NavbarView", () => {
it("deployment nav link is hidden for members", async () => {
render(
<NavbarView
proxyContextValue={proxyContextValue}
user={MockUser2}
onSignOut={noop}
canViewAuditLog={false}
@@ -2,16 +2,27 @@ import Drawer from "@mui/material/Drawer"
import IconButton from "@mui/material/IconButton"
import List from "@mui/material/List"
import ListItem from "@mui/material/ListItem"
import { makeStyles } from "@mui/styles"
import { makeStyles, useTheme } from "@mui/styles"
import MenuIcon from "@mui/icons-material/Menu"
import { CoderIcon } from "components/Icons/CoderIcon"
import { useState } from "react"
import { NavLink, useLocation } from "react-router-dom"
import { FC, useRef, useState } from "react"
import { NavLink, useLocation, useNavigate } from "react-router-dom"
import { colors } from "theme/colors"
import * as TypesGen from "../../api/typesGenerated"
import { navHeight } from "../../theme/constants"
import { combineClasses } from "../../utils/combineClasses"
import { UserDropdown } from "../UserDropdown/UsersDropdown"
import Box from "@mui/material/Box"
import Menu from "@mui/material/Menu"
import Button from "@mui/material/Button"
import MenuItem from "@mui/material/MenuItem"
import KeyboardArrowDownOutlined from "@mui/icons-material/KeyboardArrowDownOutlined"
import { ProxyContextValue } from "contexts/ProxyContext"
import { displayError } from "components/GlobalSnackbar/utils"
import Divider from "@mui/material/Divider"
import HelpOutline from "@mui/icons-material/HelpOutline"
import Tooltip from "@mui/material/Tooltip"
import Skeleton from "@mui/material/Skeleton"
export const USERS_LINK = `/users?filter=${encodeURIComponent("status:active")}`
@@ -23,6 +34,7 @@ export interface NavbarViewProps {
onSignOut: () => void
canViewAuditLog: boolean
canViewDeployment: boolean
proxyContextValue?: ProxyContextValue
}
export const Language = {
@@ -83,7 +95,7 @@ const NavItems: React.FC<
</List>
)
}
export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
export const NavbarView: FC<NavbarViewProps> = ({
user,
logo_url,
buildInfo,
@@ -91,6 +103,7 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
onSignOut,
canViewAuditLog,
canViewDeployment,
proxyContextValue,
}) => {
const styles = useStyles()
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
@@ -145,7 +158,16 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
canViewDeployment={canViewDeployment}
/>
<div className={styles.profileButton}>
<Box
display="flex"
marginLeft={{ lg: "auto" }}
gap={2}
alignItems="center"
paddingRight={2}
>
{proxyContextValue && (
<ProxyMenu proxyContextValue={proxyContextValue} />
)}
{user && (
<UserDropdown
user={user}
@@ -154,12 +176,163 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
onSignOut={onSignOut}
/>
)}
</div>
</Box>
</div>
</nav>
)
}
const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
proxyContextValue,
}) => {
const buttonRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const selectedProxy = proxyContextValue.proxy.proxy
const closeMenu = () => setIsOpen(false)
const navigate = useNavigate()
if (!proxyContextValue.isFetched) {
return (
<Skeleton
width="160px"
height={30}
sx={{ borderRadius: "4px", transform: "none" }}
/>
)
}
return (
<>
<Button
ref={buttonRef}
onClick={() => setIsOpen(true)}
size="small"
endIcon={<KeyboardArrowDownOutlined />}
sx={{
borderRadius: "4px",
"& .MuiSvgIcon-root": { fontSize: 14 },
}}
>
{selectedProxy ? (
<Box display="flex" gap={2} alignItems="center">
<Box width={14} height={14} lineHeight={0}>
<Box
component="img"
src={selectedProxy.icon_url}
alt=""
sx={{ objectFit: "contain" }}
width="100%"
height="100%"
/>
</Box>
{selectedProxy.display_name}
<ProxyStatusLatency
proxy={selectedProxy}
latency={
proxyContextValue.proxyLatencies?.[selectedProxy.id]?.latencyMS
}
/>
</Box>
) : (
"Select Proxy"
)}
</Button>
<Menu
open={isOpen}
anchorEl={buttonRef.current}
onClick={closeMenu}
onClose={closeMenu}
sx={{ "& .MuiMenu-paper": { py: 1 } }}
>
{proxyContextValue.proxies?.map((proxy) => (
<MenuItem
onClick={() => {
if (!proxy.healthy) {
displayError("Please select a healthy workspace proxy.")
closeMenu()
return
}
proxyContextValue.setProxy(proxy)
closeMenu()
}}
key={proxy.id}
selected={proxy.id === selectedProxy?.id}
sx={{
fontSize: 14,
}}
>
<Box display="flex" gap={3} alignItems="center" width="100%">
<Box width={14} height={14} lineHeight={0}>
<Box
component="img"
src={proxy.icon_url}
alt=""
sx={{ objectFit: "contain" }}
width="100%"
height="100%"
/>
</Box>
{proxy.display_name}
<ProxyStatusLatency
proxy={proxy}
latency={
proxyContextValue.proxyLatencies?.[proxy.id]?.latencyMS
}
/>
</Box>
</MenuItem>
))}
<Divider sx={{ borderColor: (theme) => theme.palette.divider }} />
<MenuItem
sx={{ fontSize: 14 }}
onClick={() => {
navigate("/settings/workspace-proxies")
}}
>
Proxy settings
</MenuItem>
</Menu>
</>
)
}
const ProxyStatusLatency: FC<{ proxy: TypesGen.Region; latency?: number }> = ({
proxy,
latency,
}) => {
const theme = useTheme()
let color = theme.palette.success.light
if (!latency) {
return (
<Tooltip title="Latency not available">
<HelpOutline
sx={{
ml: "auto",
fontSize: "14px !important",
color: (theme) => theme.palette.text.secondary,
}}
/>
</Tooltip>
)
}
if (latency >= 300) {
color = theme.palette.error.light
}
if (!proxy.healthy || latency >= 100) {
color = theme.palette.warning.light
}
return (
<Box sx={{ color, fontSize: 13, marginLeft: "auto" }}>
{latency.toFixed(0)}ms
</Box>
)
}
const useStyles = makeStyles((theme) => ({
root: {
height: navHeight,
@@ -192,12 +365,6 @@ const useStyles = makeStyles((theme) => ({
display: "flex",
},
},
profileButton: {
paddingRight: theme.spacing(2),
[theme.breakpoints.up("md")]: {
marginLeft: "auto",
},
},
mobileMenuButton: {
[theme.breakpoints.up("md")]: {
display: "none",
@@ -4,7 +4,7 @@ import { makeStyles } from "@mui/styles"
import GroupAdd from "@mui/icons-material/GroupAddOutlined"
import PersonAdd from "@mui/icons-material/PersonAddOutlined"
import { useMachine } from "@xstate/react"
import { USERS_LINK } from "components/NavbarView/NavbarView"
import { USERS_LINK } from "components/Navbar/NavbarView"
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"
import { useFeatureVisibility } from "hooks/useFeatureVisibility"
import { usePermissions } from "hooks/usePermissions"
+1 -1
View File
@@ -127,8 +127,8 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
return (
<ProxyContext.Provider
value={{
proxyLatencies,
userProxy: userSavedProxy,
proxyLatencies: proxyLatencies,
proxy: experimentEnabled
? proxy
: {
+4 -2
View File
@@ -25,8 +25,10 @@ const proxyLatenciesReducer = (
action: ProxyLatencyAction,
): Record<string, ProxyLatencyReport> => {
// Just overwrite any existing latency.
state[action.proxyID] = action.report
return state
return {
...state,
[action.proxyID]: action.report,
}
}
export const useProxyLatency = (
@@ -9,10 +9,11 @@ import {
HealthyBadge,
NotHealthyBadge,
} from "components/DeploySettingsLayout/Badges"
import { makeStyles, useTheme } from "@mui/styles"
import { makeStyles } from "@mui/styles"
import { combineClasses } from "utils/combineClasses"
import { ProxyLatencyReport } from "contexts/useProxyLatency"
import { getLatencyColor } from "utils/colors"
import { alpha } from "@mui/material/styles"
export const ProxyRow: FC<{
latency?: ProxyLatencyReport
@@ -21,7 +22,6 @@ export const ProxyRow: FC<{
preferred: boolean
}> = ({ proxy, onSelectRegion, preferred, latency }) => {
const styles = useStyles()
const theme = useTheme()
const clickable = useClickableTableRow(() => {
onSelectRegion(proxy)
@@ -47,24 +47,32 @@ export const ProxyRow: FC<{
}
avatar={
proxy.icon_url !== "" && (
<Avatar src={proxy.icon_url} variant="square" fitImage />
<Avatar
size="sm"
src={proxy.icon_url}
variant="square"
fitImage
/>
)
}
/>
</TableCell>
<TableCell>{proxy.path_app_url}</TableCell>
<TableCell>
<TableCell sx={{ fontSize: 14 }}>{proxy.path_app_url}</TableCell>
<TableCell sx={{ fontSize: 14 }}>
<ProxyStatus proxy={proxy} />
</TableCell>
<TableCell>
<span
style={{
color: latency ? getLatencyColor(theme, latency.latencyMS) : "",
}}
>
{latency ? `${latency.latencyMS.toFixed(1)} ms` : "?"}
</span>
<TableCell
sx={{
fontSize: 14,
textAlign: "right",
color: (theme) =>
latency
? getLatencyColor(theme, latency.latencyMS)
: theme.palette.text.secondary,
}}
>
{latency ? `${latency.latencyMS.toFixed(0)} ms` : "Not available"}
</TableCell>
</TableRow>
)
@@ -83,9 +91,11 @@ const ProxyStatus: FC<{
const useStyles = makeStyles((theme) => ({
preferredrow: {
// TODO: What is the best way to show what proxy is currently being used?
backgroundColor: theme.palette.secondary.main,
outline: `3px solid ${theme.palette.secondary.light}`,
outlineOffset: -3,
backgroundColor: alpha(
theme.palette.primary.main,
theme.palette.action.hoverOpacity,
),
outline: `1px solid ${theme.palette.primary.main}`,
outlineOffset: "-1px",
},
}))
@@ -50,7 +50,9 @@ export const WorkspaceProxyView: FC<
<TableCell width="40%">Proxy</TableCell>
<TableCell width="30%">URL</TableCell>
<TableCell width="10%">Status</TableCell>
<TableCell width="20%">Latency</TableCell>
<TableCell width="20%" sx={{ textAlign: "right" }}>
Latency
</TableCell>
</TableRow>
</TableHead>
<TableBody>