From 07babacec0404b45217ea3be14f803a6ce7c6f07 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 17 May 2023 14:41:13 +0000 Subject: [PATCH 1/2] Add base for proxies --- site/src/components/Navbar/Navbar.test.tsx | 2 +- site/src/components/Navbar/Navbar.tsx | 5 +- .../NavbarView.stories.tsx | 0 .../NavbarView.test.tsx | 26 ++- .../{NavbarView => Navbar}/NavbarView.tsx | 171 +++++++++++++++++- .../components/UsersLayout/UsersLayout.tsx | 2 +- site/src/contexts/ProxyContext.tsx | 2 +- 7 files changed, 193 insertions(+), 15 deletions(-) rename site/src/components/{NavbarView => Navbar}/NavbarView.stories.tsx (100%) rename site/src/components/{NavbarView => Navbar}/NavbarView.test.tsx (81%) rename site/src/components/{NavbarView => Navbar}/NavbarView.tsx (56%) diff --git a/site/src/components/Navbar/Navbar.test.tsx b/site/src/components/Navbar/Navbar.test.tsx index ca7001af5dad7..8bdac69e67443 100644 --- a/site/src/components/Navbar/Navbar.test.tsx +++ b/site/src/components/Navbar/Navbar.test.tsx @@ -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, diff --git a/site/src/components/Navbar/Navbar.tsx b/site/src/components/Navbar/Navbar.tsx index 3e25eed4cb0c0..f34108da33de4 100644 --- a/site/src/components/Navbar/Navbar.tsx +++ b/site/src/components/Navbar/Navbar.tsx @@ -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,7 @@ export const Navbar: FC = () => { featureVisibility["audit_log"] && Boolean(permissions.viewAuditLog) const canViewDeployment = Boolean(permissions.viewDeploymentValues) const onSignOut = () => authSend("SIGN_OUT") + const proxyContextValue = useProxy() return ( { onSignOut={onSignOut} canViewAuditLog={canViewAuditLog} canViewDeployment={canViewDeployment} + proxyContextValue={proxyContextValue} /> ) } diff --git a/site/src/components/NavbarView/NavbarView.stories.tsx b/site/src/components/Navbar/NavbarView.stories.tsx similarity index 100% rename from site/src/components/NavbarView/NavbarView.stories.tsx rename to site/src/components/Navbar/NavbarView.stories.tsx diff --git a/site/src/components/NavbarView/NavbarView.test.tsx b/site/src/components/Navbar/NavbarView.test.tsx similarity index 81% rename from site/src/components/NavbarView/NavbarView.test.tsx rename to site/src/components/Navbar/NavbarView.test.tsx index cae48b7a8ddcf..41f50487590b2 100644 --- a/site/src/components/NavbarView/NavbarView.test.tsx +++ b/site/src/components/Navbar/NavbarView.test.tsx @@ -1,7 +1,23 @@ 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" + +const proxyContextValue: ProxyContextValue = { + proxy: { + preferredPathAppURL: "", + preferredWildcardHostname: "", + selectedProxy: MockPrimaryWorkspaceProxy, + }, + isLoading: false, + isFetched: true, + setProxy: jest.fn(), +} describe("NavbarView", () => { const noop = () => { @@ -23,6 +39,7 @@ describe("NavbarView", () => { it("workspaces nav link has the correct href", async () => { render( { it("templates nav link has the correct href", async () => { render( { it("users nav link has the correct href", async () => { render( { // When render( { it("audit nav link has the correct href", async () => { render( { it("audit nav link is hidden for members", async () => { render( { it("deployment nav link has the correct href", async () => { render( { it("deployment nav link is hidden for members", async () => { render( void canViewAuditLog: boolean canViewDeployment: boolean + proxyContextValue: ProxyContextValue } export const Language = { @@ -83,7 +96,7 @@ const NavItems: React.FC< ) } -export const NavbarView: React.FC> = ({ +export const NavbarView: FC = ({ user, logo_url, buildInfo, @@ -91,6 +104,7 @@ export const NavbarView: React.FC> = ({ onSignOut, canViewAuditLog, canViewDeployment, + proxyContextValue, }) => { const styles = useStyles() const [isDrawerOpen, setIsDrawerOpen] = useState(false) @@ -145,7 +159,14 @@ export const NavbarView: React.FC> = ({ canViewDeployment={canViewDeployment} /> -
+ + {user && ( > = ({ onSignOut={onSignOut} /> )} -
+ ) } +const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ + proxyContextValue, +}) => { + const buttonRef = useRef(null) + const [isOpen, setIsOpen] = useState(false) + const selectedProxy = proxyContextValue.proxy.selectedProxy + + const closeMenu = () => setIsOpen(false) + + return ( + <> + + + {proxyContextValue.proxies?.map((proxy) => ( + { + if (!proxy.healthy) { + displayError("Please select a healthy workspace proxy.") + closeMenu() + return + } + + proxyContextValue.setProxy(proxy) + closeMenu() + }} + key={proxy.id} + selected={proxy.id === proxyContextValue.proxy.selectedProxy?.id} + sx={{ + "& .MuiSvgIcon-root": { fontSize: 16 }, + }} + > + + + + + {proxy.display_name} + + + + ))} + + + ) +} + +const ProxyStatusIcon: FC< + { proxy: TypesGen.Region; latency: number } & SvgIconProps +> = ({ proxy, latency, ...svgProps }) => { + if (!proxy.healthy) { + return ( + theme.palette.warning.light, ...svgProps.sx }} + /> + ) + } + + if (latency >= 150 && latency < 300) { + return ( + theme.palette.warning.light, ...svgProps.sx }} + /> + ) + } + + if (latency >= 300) { + return ( + theme.palette.error.light, ...svgProps.sx }} + /> + ) + } + + return ( + theme.palette.success.light, ...svgProps.sx }} + /> + ) +} + const useStyles = makeStyles((theme) => ({ root: { height: navHeight, @@ -192,12 +349,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", diff --git a/site/src/components/UsersLayout/UsersLayout.tsx b/site/src/components/UsersLayout/UsersLayout.tsx index e87d651840a54..a4eacd7b80d7d 100644 --- a/site/src/components/UsersLayout/UsersLayout.tsx +++ b/site/src/components/UsersLayout/UsersLayout.tsx @@ -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" diff --git a/site/src/contexts/ProxyContext.tsx b/site/src/contexts/ProxyContext.tsx index 66390164809a6..0c9b0693ac280 100644 --- a/site/src/contexts/ProxyContext.tsx +++ b/site/src/contexts/ProxyContext.tsx @@ -11,7 +11,7 @@ import { } from "react" import { ProxyLatencyReport, useProxyLatency } from "./useProxyLatency" -interface ProxyContextValue { +export interface ProxyContextValue { proxy: PreferredProxy proxies?: Region[] proxyLatencies?: Record From a096f9fdd8e2dcee760d8cab9dba455ab83c7529 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 30 May 2023 13:41:37 +0000 Subject: [PATCH 2/2] Add proxy navbar menu --- site/src/components/Navbar/Navbar.tsx | 5 +- .../src/components/Navbar/NavbarView.test.tsx | 5 +- site/src/components/Navbar/NavbarView.tsx | 116 ++++++++++-------- site/src/contexts/ProxyContext.tsx | 2 +- site/src/contexts/useProxyLatency.ts | 6 +- .../WorkspaceProxyPage/WorkspaceProxyRow.tsx | 44 ++++--- .../WorkspaceProxyPage/WorkspaceProxyView.tsx | 4 +- 7 files changed, 109 insertions(+), 73 deletions(-) diff --git a/site/src/components/Navbar/Navbar.tsx b/site/src/components/Navbar/Navbar.tsx index f34108da33de4..88042b5bceb8d 100644 --- a/site/src/components/Navbar/Navbar.tsx +++ b/site/src/components/Navbar/Navbar.tsx @@ -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 ( { onSignOut={onSignOut} canViewAuditLog={canViewAuditLog} canViewDeployment={canViewDeployment} - proxyContextValue={proxyContextValue} + proxyContextValue={ + dashboard.experiments.includes("moons") ? proxyContextValue : undefined + } /> ) } diff --git a/site/src/components/Navbar/NavbarView.test.tsx b/site/src/components/Navbar/NavbarView.test.tsx index 41f50487590b2..7c5bf6507fd78 100644 --- a/site/src/components/Navbar/NavbarView.test.tsx +++ b/site/src/components/Navbar/NavbarView.test.tsx @@ -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", () => { diff --git a/site/src/components/Navbar/NavbarView.tsx b/site/src/components/Navbar/NavbarView.tsx index 861bcceaf804c..176a9ae446aad 100644 --- a/site/src/components/Navbar/NavbarView.tsx +++ b/site/src/components/Navbar/NavbarView.tsx @@ -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" @@ -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")}` @@ -35,7 +34,7 @@ export interface NavbarViewProps { onSignOut: () => void canViewAuditLog: boolean canViewDeployment: boolean - proxyContextValue: ProxyContextValue + proxyContextValue?: ProxyContextValue } export const Language = { @@ -166,7 +165,9 @@ export const NavbarView: FC = ({ alignItems="center" paddingRight={2} > - + {proxyContextValue && ( + + )} {user && ( = ({ }) => { const buttonRef = useRef(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 ( + + ) + } return ( <> @@ -198,11 +209,12 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ size="small" endIcon={} sx={{ + borderRadius: "4px", "& .MuiSvgIcon-root": { fontSize: 14 }, }} > {selectedProxy ? ( - + = ({ /> {selectedProxy.display_name} - @@ -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) => ( = ({ closeMenu() }} key={proxy.id} - selected={proxy.id === proxyContextValue.proxy.selectedProxy?.id} + selected={proxy.id === selectedProxy?.id} sx={{ - "& .MuiSvgIcon-root": { fontSize: 16 }, + fontSize: 14, }} > - - + + = ({ /> {proxy.display_name} - ))} + theme.palette.divider }} /> + { + navigate("/settings/workspace-proxies") + }} + > + Proxy settings + ) } -const ProxyStatusIcon: FC< - { proxy: TypesGen.Region; latency: number } & SvgIconProps -> = ({ proxy, latency, ...svgProps }) => { - if (!proxy.healthy) { - return ( - 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 ( - theme.palette.warning.light, ...svgProps.sx }} - /> + + theme.palette.text.secondary, + }} + /> + ) } if (latency >= 300) { - return ( - theme.palette.error.light, ...svgProps.sx }} - /> - ) + color = theme.palette.error.light + } + + if (!proxy.healthy || latency >= 100) { + color = theme.palette.warning.light } return ( - theme.palette.success.light, ...svgProps.sx }} - /> + + {latency.toFixed(0)}ms + ) } diff --git a/site/src/contexts/ProxyContext.tsx b/site/src/contexts/ProxyContext.tsx index 32d3e3ae11d63..42de93234d9f7 100644 --- a/site/src/contexts/ProxyContext.tsx +++ b/site/src/contexts/ProxyContext.tsx @@ -127,8 +127,8 @@ export const ProxyProvider: FC = ({ children }) => { return ( => { // Just overwrite any existing latency. - state[action.proxyID] = action.report - return state + return { + ...state, + [action.proxyID]: action.report, + } } export const useProxyLatency = ( diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx index f23cb7ba8e727..8db1e9493af39 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx @@ -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 !== "" && ( - + ) } /> - {proxy.path_app_url} - + {proxy.path_app_url} + - - - {latency ? `${latency.latencyMS.toFixed(1)} ms` : "?"} - + + latency + ? getLatencyColor(theme, latency.latencyMS) + : theme.palette.text.secondary, + }} + > + {latency ? `${latency.latencyMS.toFixed(0)} ms` : "Not available"} ) @@ -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", }, })) diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx index 0ae853201328d..fc5ccf87d155e 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx @@ -50,7 +50,9 @@ export const WorkspaceProxyView: FC< Proxy URL Status - Latency + + Latency +