Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add proxy navbar menu
  • Loading branch information
BrunoQuaresma committed May 30, 2023
commit a096f9fdd8e2dcee760d8cab9dba455ab83c7529
5 changes: 4 additions & 1 deletion site/src/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const Navbar: FC = () => {
const canViewDeployment = Boolean(permissions.viewDeploymentValues)
const onSignOut = () => authSend("SIGN_OUT")
const proxyContextValue = useProxy()
const dashboard = useDashboard()

return (
<NavbarView
Expand All @@ -28,7 +29,9 @@ export const Navbar: FC = () => {
onSignOut={onSignOut}
canViewAuditLog={canViewAuditLog}
canViewDeployment={canViewDeployment}
proxyContextValue={proxyContextValue}
proxyContextValue={
dashboard.experiments.includes("moons") ? proxyContextValue : undefined
}
/>
)
}
5 changes: 4 additions & 1 deletion site/src/components/Navbar/NavbarView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ import {
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: "",
selectedProxy: MockPrimaryWorkspaceProxy,
proxy: MockPrimaryWorkspaceProxy,
},
isLoading: false,
isFetched: true,
setProxy: jest.fn(),
clearProxy: action("clearProxy"),
proxyLatencies: {},
}

describe("NavbarView", () => {
Expand Down
116 changes: 66 additions & 50 deletions site/src/components/Navbar/NavbarView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ 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 { FC, useRef, useState } from "react"
import { NavLink, useLocation } from "react-router-dom"
import { NavLink, useLocation, useNavigate } from "react-router-dom"
import { colors } from "theme/colors"
import * as TypesGen from "../../api/typesGenerated"
import { navHeight } from "../../theme/constants"
Expand All @@ -19,11 +19,10 @@ 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 SignalCellular1BarOutlined from "@mui/icons-material/SignalCellular1BarOutlined"
import SignalCellular2BarOutlined from "@mui/icons-material/SignalCellular2BarOutlined"
import SignalCellular4BarOutlined from "@mui/icons-material/SignalCellular4BarOutlined"
import SignalCellularConnectedNoInternet0BarOutlined from "@mui/icons-material/SignalCellularConnectedNoInternet0BarOutlined"
import { SvgIconProps } from "@mui/material/SvgIcon"
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")}`

Expand All @@ -35,7 +34,7 @@ export interface NavbarViewProps {
onSignOut: () => void
canViewAuditLog: boolean
canViewDeployment: boolean
proxyContextValue: ProxyContextValue
proxyContextValue?: ProxyContextValue
}

export const Language = {
Expand Down Expand Up @@ -166,7 +165,9 @@ export const NavbarView: FC<NavbarViewProps> = ({
alignItems="center"
paddingRight={2}
>
<ProxyMenu proxyContextValue={proxyContextValue} />
{proxyContextValue && (
<ProxyMenu proxyContextValue={proxyContextValue} />
)}
{user && (
<UserDropdown
user={user}
Expand All @@ -186,9 +187,19 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
}) => {
const buttonRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const selectedProxy = proxyContextValue.proxy.selectedProxy

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 (
<>
Expand All @@ -198,11 +209,12 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
size="small"
endIcon={<KeyboardArrowDownOutlined />}
sx={{
borderRadius: "4px",
"& .MuiSvgIcon-root": { fontSize: 14 },
}}
>
{selectedProxy ? (
<Box display="flex" gap={1} alignItems="center">
<Box display="flex" gap={2} alignItems="center">
<Box width={14} height={14} lineHeight={0}>
<Box
component="img"
Expand All @@ -214,11 +226,10 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
/>
</Box>
{selectedProxy.display_name}
<ProxyStatusIcon
<ProxyStatusLatency
proxy={selectedProxy}
latency={
proxyContextValue.proxyLatencies?.[selectedProxy.id]
?.latencyMS ?? 0
proxyContextValue.proxyLatencies?.[selectedProxy.id]?.latencyMS
}
/>
</Box>
Expand All @@ -231,6 +242,7 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
anchorEl={buttonRef.current}
onClick={closeMenu}
onClose={closeMenu}
sx={{ "& .MuiMenu-paper": { py: 1 } }}
>
{proxyContextValue.proxies?.map((proxy) => (
<MenuItem
Expand All @@ -245,13 +257,13 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
closeMenu()
}}
key={proxy.id}
selected={proxy.id === proxyContextValue.proxy.selectedProxy?.id}
selected={proxy.id === selectedProxy?.id}
sx={{
"& .MuiSvgIcon-root": { fontSize: 16 },
fontSize: 14,
}}
>
<Box display="flex" gap={2} alignItems="center" width="100%">
<Box width={16} height={16} lineHeight={0}>
<Box display="flex" gap={3} alignItems="center" width="100%">
<Box width={14} height={14} lineHeight={0}>
<Box
component="img"
src={proxy.icon_url}
Expand All @@ -262,58 +274,62 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
/>
</Box>
{proxy.display_name}
<ProxyStatusIcon
<ProxyStatusLatency
proxy={proxy}
latency={
proxyContextValue.proxyLatencies?.[proxy.id]?.latencyMS ?? 0
proxyContextValue.proxyLatencies?.[proxy.id]?.latencyMS
}
sx={{
marginLeft: "auto",
}}
/>
</Box>
</MenuItem>
))}
<Divider sx={{ borderColor: (theme) => theme.palette.divider }} />
<MenuItem
sx={{ fontSize: 14 }}
onClick={() => {
navigate("/settings/workspace-proxies")
}}
>
Proxy settings
</MenuItem>
</Menu>
</>
)
}

const ProxyStatusIcon: FC<
{ proxy: TypesGen.Region; latency: number } & SvgIconProps
> = ({ proxy, latency, ...svgProps }) => {
if (!proxy.healthy) {
return (
<SignalCellularConnectedNoInternet0BarOutlined
{...svgProps}
sx={{ color: (theme) => theme.palette.warning.light, ...svgProps.sx }}
/>
)
}
const ProxyStatusLatency: FC<{ proxy: TypesGen.Region; latency?: number }> = ({
proxy,
latency,
}) => {
const theme = useTheme()
let color = theme.palette.success.light

if (latency >= 150 && latency < 300) {
if (!latency) {
return (
<SignalCellular2BarOutlined
{...svgProps}
sx={{ color: (theme) => theme.palette.warning.light, ...svgProps.sx }}
/>
<Tooltip title="Latency not available">
<HelpOutline
sx={{
ml: "auto",
fontSize: "14px !important",
color: (theme) => theme.palette.text.secondary,
}}
/>
</Tooltip>
)
}

if (latency >= 300) {
return (
<SignalCellular1BarOutlined
{...svgProps}
sx={{ color: (theme) => theme.palette.error.light, ...svgProps.sx }}
/>
)
color = theme.palette.error.light
}

if (!proxy.healthy || latency >= 100) {
color = theme.palette.warning.light
}

return (
<SignalCellular4BarOutlined
{...svgProps}
sx={{ color: (theme) => theme.palette.success.light, ...svgProps.sx }}
/>
<Box sx={{ color, fontSize: 13, marginLeft: "auto" }}>
{latency.toFixed(0)}ms
</Box>
)
}

Expand Down
2 changes: 1 addition & 1 deletion site/src/contexts/ProxyContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ export const ProxyProvider: FC<PropsWithChildren> = ({ children }) => {
return (
<ProxyContext.Provider
value={{
proxyLatencies,
userProxy: userSavedProxy,
proxyLatencies: proxyLatencies,
proxy: experimentEnabled
? proxy
: {
Expand Down
6 changes: 4 additions & 2 deletions site/src/contexts/useProxyLatency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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>
)
Expand All @@ -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",
},
}))
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down