1- import { DeploymentStats , WorkspaceStatus } from "api/typesGenerated" ;
2- import { FC , useMemo , useEffect , useState } from "react" ;
1+ import type { Health } from "api/api" ;
2+ import type { DeploymentStats , WorkspaceStatus } from "api/typesGenerated" ;
3+ import {
4+ type FC ,
5+ useMemo ,
6+ useEffect ,
7+ useState ,
8+ PropsWithChildren ,
9+ } from "react" ;
310import prettyBytes from "pretty-bytes" ;
411import BuildingIcon from "@mui/icons-material/Build" ;
5- import { RocketIcon } from "components/Icons/RocketIcon" ;
6- import { MONOSPACE_FONT_FAMILY } from "theme/constants" ;
712import Tooltip from "@mui/material/Tooltip" ;
813import { Link as RouterLink } from "react-router-dom" ;
914import Link from "@mui/material/Link" ;
@@ -12,13 +17,26 @@ import DownloadIcon from "@mui/icons-material/CloudDownload";
1217import UploadIcon from "@mui/icons-material/CloudUpload" ;
1318import LatencyIcon from "@mui/icons-material/SettingsEthernet" ;
1419import WebTerminalIcon from "@mui/icons-material/WebAsset" ;
15- import { TerminalIcon } from "components/Icons/TerminalIcon" ;
16- import dayjs from "dayjs" ;
1720import CollectedIcon from "@mui/icons-material/Compare" ;
1821import RefreshIcon from "@mui/icons-material/Refresh" ;
1922import Button from "@mui/material/Button" ;
23+ import { css as className } from "@emotion/css" ;
24+ import {
25+ css ,
26+ type CSSObject ,
27+ type Theme ,
28+ type Interpolation ,
29+ useTheme ,
30+ } from "@emotion/react" ;
31+ import dayjs from "dayjs" ;
32+ import { TerminalIcon } from "components/Icons/TerminalIcon" ;
33+ import { RocketIcon } from "components/Icons/RocketIcon" ;
34+ import ErrorIcon from "@mui/icons-material/ErrorOutline" ;
35+ import { MONOSPACE_FONT_FAMILY } from "theme/constants" ;
2036import { getDisplayWorkspaceStatus } from "utils/workspace" ;
21- import { css , type Theme , type Interpolation , useTheme } from "@emotion/react" ;
37+ import { colors } from "theme/colors" ;
38+ import { HelpTooltipTitle } from "components/HelpTooltip/HelpTooltip" ;
39+ import { Stack } from "components/Stack/Stack" ;
2240
2341export const bannerHeight = 36 ;
2442
@@ -49,14 +67,13 @@ const styles = {
4967} satisfies Record < string , Interpolation < Theme > > ;
5068
5169export interface DeploymentBannerViewProps {
52- fetchStats ?: ( ) => void ;
70+ health ?: Health ;
5371 stats ?: DeploymentStats ;
72+ fetchStats ?: ( ) => void ;
5473}
5574
56- export const DeploymentBannerView : FC < DeploymentBannerViewProps > = ( {
57- stats,
58- fetchStats,
59- } ) => {
75+ export const DeploymentBannerView : FC < DeploymentBannerViewProps > = ( props ) => {
76+ const { health, stats, fetchStats } = props ;
6077 const theme = useTheme ( ) ;
6178 const aggregatedMinutes = useMemo ( ( ) => {
6279 if ( ! stats ) {
@@ -105,14 +122,43 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
105122 // eslint-disable-next-line react-hooks/exhaustive-deps -- We want this to periodically update!
106123 } , [ timeUntilRefresh , stats ] ) ;
107124
125+ const unhealthy = health && ! health . healthy ;
126+
127+ const statusBadgeStyle = css `
128+ display : flex;
129+ align-items : center;
130+ justify-content : center;
131+ background-color : ${ unhealthy ? colors . red [ 10 ] : undefined } ;
132+ padding : ${ theme . spacing ( 0 , 1.5 ) } ;
133+ height : ${ bannerHeight } px;
134+ color : # fff ;
135+
136+ & svg {
137+ width : 16px ;
138+ height : 16px ;
139+ }
140+ ` ;
141+
142+ const statusSummaryStyle = className `
143+ ${ theme . typography . body2 as CSSObject }
144+
145+ margin : ${ theme . spacing ( 0 , 0 , 0.5 , 1.5 ) } ;
146+ width : ${ theme . spacing ( 50 ) } ;
147+ padding : ${ theme . spacing ( 2 ) } ;
148+ color : ${ theme . palette . text . primary } ;
149+ background-color : ${ theme . palette . background . paper } ;
150+ border : 1px solid ${ theme . palette . divider } ;
151+ pointer-events : none;
152+ ` ;
153+
108154 return (
109155 < div
110156 css = { {
111157 position : "sticky" ,
112158 height : bannerHeight ,
113159 bottom : 0 ,
114160 zIndex : 1 ,
115- padding : theme . spacing ( 0 , 2 ) ,
161+ paddingRight : theme . spacing ( 2 ) ,
116162 backgroundColor : theme . palette . background . paper ,
117163 display : "flex" ,
118164 alignItems : "center" ,
@@ -124,24 +170,51 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
124170 whiteSpace : "nowrap" ,
125171 } }
126172 >
127- < Tooltip title = "Status of your Coder deployment. Only visible for admins!" >
128- < div
129- css = { css `
130- display : flex;
131- align-items : center;
132-
133- & svg {
134- width : 16px ;
135- height : 16px ;
136- }
137-
138- ${ theme . breakpoints . down ( "lg" ) } {
139- display : none;
140- }
141- ` }
142- >
143- < RocketIcon />
144- </ div >
173+ < Tooltip
174+ classes = { { tooltip : statusSummaryStyle } }
175+ title = {
176+ unhealthy ? (
177+ < >
178+ < HelpTooltipTitle >
179+ We have detected problems with your Coder deployment.
180+ </ HelpTooltipTitle >
181+ < Stack spacing = { 1 } >
182+ { health . access_url && (
183+ < HealthIssue >
184+ Your access URL may be configured incorrectly.
185+ </ HealthIssue >
186+ ) }
187+ { health . database && (
188+ < HealthIssue > Your database is unhealthy.</ HealthIssue >
189+ ) }
190+ { health . derp && (
191+ < HealthIssue >
192+ We're noticing DERP proxy issues.
193+ </ HealthIssue >
194+ ) }
195+ { health . websocket && (
196+ < HealthIssue >
197+ We're noticing websocket issues.
198+ </ HealthIssue >
199+ ) }
200+ </ Stack >
201+ </ >
202+ ) : (
203+ < > Status of your Coder deployment. Only visible for admins!</ >
204+ )
205+ }
206+ open = { process . env . STORYBOOK === "true" ? true : undefined }
207+ css = { { marginRight : theme . spacing ( - 2 ) } }
208+ >
209+ { unhealthy ? (
210+ < Link component = { RouterLink } to = "/health" css = { statusBadgeStyle } >
211+ < ErrorIcon />
212+ </ Link >
213+ ) : (
214+ < div css = { statusBadgeStyle } >
215+ < RocketIcon />
216+ </ div >
217+ ) }
145218 </ Tooltip >
146219 < div css = { styles . group } >
147220 < div css = { styles . category } > Workspaces</ div >
@@ -330,3 +403,12 @@ const WorkspaceBuildValue: FC<{
330403 </ Tooltip >
331404 ) ;
332405} ;
406+
407+ const HealthIssue : FC < PropsWithChildren > = ( { children } ) => {
408+ return (
409+ < Stack direction = "row" spacing = { 1 } >
410+ < ErrorIcon fontSize = "small" htmlColor = { colors . red [ 10 ] } />
411+ { children }
412+ </ Stack >
413+ ) ;
414+ } ;
0 commit comments