diff --git a/site/src/components/Link/Link.tsx b/site/src/components/Link/Link.tsx index c68ac1d70d..34af1b71e1 100644 --- a/site/src/components/Link/Link.tsx +++ b/site/src/components/Link/Link.tsx @@ -27,10 +27,14 @@ interface LinkProps extends React.AnchorHTMLAttributes, VariantProps { asChild?: boolean; + showExternalIcon?: boolean; } export const Link = forwardRef( - ({ className, children, size, asChild, ...props }, ref) => { + ( + { className, children, size, asChild, showExternalIcon = true, ...props }, + ref, + ) => { const Comp = asChild ? Slot : "a"; return ( ( {...props} > {children} - ); }, diff --git a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx index 949aa0390e..b5bb44f76d 100644 --- a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx +++ b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx @@ -3,6 +3,7 @@ import { MockDeploymentStats, } from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, screen, userEvent, waitFor, within } from "storybook/test"; import { DeploymentBannerView } from "./DeploymentBannerView"; const meta: Meta = { @@ -22,6 +23,14 @@ export const WithHealthIssues: Story = { args: { health: DeploymentHealthUnhealthy, }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByTestId("deployment-health-trigger"); + await userEvent.hover(trigger); + await waitFor(() => + expect(screen.getByRole("tooltip")).toBeInTheDocument(), + ); + }, }; export const WithDismissedHealthIssues: Story = { @@ -34,4 +43,12 @@ export const WithDismissedHealthIssues: Story = { }, }, }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByTestId("deployment-health-trigger"); + await userEvent.hover(trigger); + await waitFor(() => + expect(screen.getByRole("tooltip")).toBeInTheDocument(), + ); + }, }; diff --git a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx index eccd9ad6b6..de367f12c1 100644 --- a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx +++ b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx @@ -1,6 +1,3 @@ -import { css, type Interpolation, type Theme, useTheme } from "@emotion/react"; -import Link from "@mui/material/Link"; -import Tooltip from "@mui/material/Tooltip"; import type { DeploymentStats, HealthcheckReport, @@ -12,7 +9,13 @@ import { JetBrainsIcon } from "components/Icons/JetBrainsIcon"; import { RocketIcon } from "components/Icons/RocketIcon"; import { TerminalIcon } from "components/Icons/TerminalIcon"; import { VSCodeIcon } from "components/Icons/VSCodeIcon"; -import { Stack } from "components/Stack/Stack"; +import { Link } from "components/Link/Link"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import dayjs from "dayjs"; import { AppWindowIcon, @@ -33,12 +36,8 @@ import { useState, } from "react"; import { Link as RouterLink } from "react-router"; -import { MONOSPACE_FONT_FAMILY } from "theme/constants"; -import colors from "theme/tailwindColors"; import { getDisplayWorkspaceStatus } from "utils/workspace"; -const bannerHeight = 36; - interface DeploymentBannerViewProps { health?: HealthcheckReport; stats?: DeploymentStats; @@ -50,8 +49,6 @@ export const DeploymentBannerView: FC = ({ stats, fetchStats, }) => { - const theme = useTheme(); - const aggregatedMinutes = useMemo(() => { if (!stats) { return; @@ -105,67 +102,60 @@ export const DeploymentBannerView: FC = ({ return (
- 0 ? ( - <> - - We have detected problems with your Coder deployment. - - - {healthErrors.map((error) => ( - {error} - ))} - - - ) : ( - "Status of your Coder deployment. Only visible for admins!" - ) - } - open={process.env.STORYBOOK === "true" ? true : undefined} - css={{ marginRight: -16 }} - > - {healthErrors.length > 0 ? ( - + + + {healthErrors.length > 0 ? ( + + + + + + ) : ( +
+ +
+ )} +
+ - - - ) : ( -
- -
- )} -
+ {healthErrors.length > 0 ? ( + <> + + We have detected problems with your Coder deployment. + +
+ {healthErrors.map((error) => ( + {error} + ))} +
+ + ) : ( + "Status of your Coder deployment. Only visible for admins!" + )} + +
+ -
-
Workspaces
-
+
+
Workspaces
+
= ({
-
- -
Transmission
-
- -
- -
- - {stats ? prettyBytes(stats.workspaces.rx_bytes) : "-"} -
+
+ + + +
Transmission
+
+ + {`Activity in the last ~${aggregatedMinutes} minutes`} +
+
+
+ + + +
+ + {stats ? prettyBytes(stats.workspaces.rx_bytes) : "-"} +
+
+ Data sent to workspaces +
+
- -
- - {stats ? prettyBytes(stats.workspaces.tx_bytes) : "-"} -
-
+ + + +
+ + {stats ? prettyBytes(stats.workspaces.tx_bytes) : "-"} +
+
+ Data sent from workspaces +
+
- -
- - {displayLatency > 0 ? `${displayLatency?.toFixed(2)} ms` : "-"} -
-
+ + + +
+ + {displayLatency > 0 + ? `${displayLatency?.toFixed(2)} ms` + : "-"} +
+
+ + {displayLatency < 0 + ? "No recent workspace connections have been made" + : "The average latency of user connections to workspaces"} + +
+
-
-
Active Connections
+
+
Active Connections
-
- -
- - {typeof stats?.session_count.vscode === "undefined" - ? "-" - : stats?.session_count.vscode} -
-
+
+ + + +
+ + {typeof stats?.session_count.vscode === "undefined" + ? "-" + : stats?.session_count.vscode} +
+
+ + VS Code Editors with the Coder Remote Extension + +
+
- -
- - {typeof stats?.session_count.jetbrains === "undefined" - ? "-" - : stats?.session_count.jetbrains} -
-
+ + + +
+ + {typeof stats?.session_count.jetbrains === "undefined" + ? "-" + : stats?.session_count.jetbrains} +
+
+ JetBrains Editors +
+
- -
- - {typeof stats?.session_count.ssh === "undefined" - ? "-" - : stats?.session_count.ssh} -
-
+ + + +
+ + {typeof stats?.session_count.ssh === "undefined" + ? "-" + : stats?.session_count.ssh} +
+
+ SSH Sessions +
+
- -
- - {typeof stats?.session_count.reconnecting_pty === "undefined" - ? "-" - : stats?.session_count.reconnecting_pty} -
-
+ + + +
+ + {typeof stats?.session_count.reconnecting_pty === "undefined" + ? "-" + : stats?.session_count.reconnecting_pty} +
+
+ Web Terminal Sessions +
+
-
- -
- - {lastAggregated} -
-
+
+ + + +
+ + {lastAggregated} +
+
+ + The last time stats were aggregated. Workspaces report statistics + periodically, so it may take a bit for these to update! + +
+
- - - + + + + + + + A countdown until stats are fetched again. Click to refresh! + + +
); @@ -352,35 +370,36 @@ const WorkspaceBuildValue: FC = ({ } return ( - - -
- {icon} - {typeof count === "undefined" ? "-" : count} -
- -
+ + + + + +
+ {icon} + {typeof count === "undefined" ? "-" : count} +
+
+ +
+ {`${statusText} Workspaces`} +
+
); }; const ValueSeparator: FC = () => { - return
/
; + return
/
; }; const HealthIssue: FC = ({ children }) => { - const theme = useTheme(); - return ( - - +
+ {children} - +
); }; @@ -409,48 +428,3 @@ const getHealthErrors = (health: HealthcheckReport) => { return warnings; }; - -const styles = { - statusBadge: (theme) => css` - display: flex; - align-items: center; - justify-content: center; - padding: 0 12px; - height: 100%; - color: ${theme.experimental.l1.text}; - - & svg { - width: 16px; - height: 16px; - } - `, - unhealthy: { - backgroundColor: colors.red[700], - }, - group: css` - display: flex; - align-items: center; - `, - category: (theme) => ({ - marginRight: 16, - color: theme.palette.text.primary, - }), - values: (theme) => ({ - display: "flex", - gap: 8, - color: theme.palette.text.secondary, - }), - value: css` - display: flex; - align-items: center; - gap: 4px; - - & svg { - width: 12px; - height: 12px; - } - `, - separator: (theme) => ({ - color: theme.palette.text.disabled, - }), -} satisfies Record>;