Skip to content

Commit cf35c0d

Browse files
feat(site): add health warning and a health monitor page (#8844)
1 parent 44f9b02 commit cf35c0d

File tree

17 files changed

+814
-15
lines changed

17 files changed

+814
-15
lines changed

coderd/apidoc/docs.go

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codersdk/deployment.go

+4
Original file line numberDiff line numberDiff line change
@@ -1871,6 +1871,9 @@ const (
18711871
// Insights page
18721872
ExperimentTemplateInsightsPage Experiment = "template_insights_page"
18731873

1874+
// Deployment health page
1875+
ExperimentDeploymentHealthPage Experiment = "deployment_health_page"
1876+
18741877
// Add new experiments here!
18751878
// ExperimentExample Experiment = "example"
18761879
)
@@ -1881,6 +1884,7 @@ const (
18811884
// not be included here and will be essentially hidden.
18821885
var ExperimentsAll = Experiments{
18831886
ExperimentTemplateInsightsPage,
1887+
ExperimentDeploymentHealthPage,
18841888
}
18851889

18861890
// Experiments is a list of experiments that are enabled for the deployment.

docs/api/schemas.md

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/AppRouter.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ const TemplateInsightsPage = lazy(
183183
() =>
184184
import("./pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage"),
185185
)
186+
const HealthPage = lazy(() => import("./pages/HealthPage/HealthPage"))
186187

187188
export const AppRouter: FC = () => {
188189
return (
@@ -197,6 +198,8 @@ export const AppRouter: FC = () => {
197198
<Route element={<DashboardLayout />}>
198199
<Route index element={<IndexPage />} />
199200

201+
<Route path="health" element={<HealthPage />} />
202+
200203
<Route path="gitauth/:provider" element={<GitAuthPage />} />
201204

202205
<Route path="workspaces" element={<WorkspacesPage />} />

site/src/api/api.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1405,3 +1405,15 @@ export const getInsightsTemplate = async (
14051405
const response = await axios.get(`/api/v2/insights/templates?${params}`)
14061406
return response.data
14071407
}
1408+
1409+
export const getHealth = () => {
1410+
return axios.get<{
1411+
healthy: boolean
1412+
time: string
1413+
coder_version: string
1414+
derp: { healthy: boolean }
1415+
access_url: { healthy: boolean }
1416+
websocket: { healthy: boolean }
1417+
database: { healthy: boolean }
1418+
}>("/api/v2/debug/health")
1419+
}

site/src/api/typesGenerated.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/components/Dashboard/DashboardLayout.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Box from "@mui/material/Box"
1616
import InfoOutlined from "@mui/icons-material/InfoOutlined"
1717
import Button from "@mui/material/Button"
1818
import { docs } from "utils/docs"
19+
import { HealthBanner } from "./HealthBanner"
1920

2021
export const DashboardLayout: FC = () => {
2122
const styles = useStyles()
@@ -30,6 +31,7 @@ export const DashboardLayout: FC = () => {
3031

3132
return (
3233
<>
34+
<HealthBanner />
3335
<ServiceBanner />
3436
{canViewDeployment && <LicenseBanner />}
3537

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Alert } from "components/Alert/Alert"
2+
import { Link as RouterLink } from "react-router-dom"
3+
import Link from "@mui/material/Link"
4+
import { colors } from "theme/colors"
5+
import { useQuery } from "@tanstack/react-query"
6+
import { getHealth } from "api/api"
7+
import { useDashboard } from "./DashboardProvider"
8+
9+
export const HealthBanner = () => {
10+
const { data: healthStatus } = useQuery({
11+
queryKey: ["health"],
12+
queryFn: () => getHealth(),
13+
})
14+
const dashboard = useDashboard()
15+
const hasHealthIssues = healthStatus && !healthStatus.data.healthy
16+
17+
if (
18+
dashboard.experiments.includes("deployment_health_page") &&
19+
hasHealthIssues
20+
) {
21+
return (
22+
<Alert
23+
severity="error"
24+
variant="filled"
25+
sx={{
26+
border: 0,
27+
borderRadius: 0,
28+
backgroundColor: colors.red[10],
29+
}}
30+
>
31+
We have detected problems with your Coder deployment. Please{" "}
32+
<Link
33+
component={RouterLink}
34+
to="/health"
35+
sx={{ fontWeight: 600, color: "inherit" }}
36+
>
37+
inspect the health status
38+
</Link>
39+
.
40+
</Alert>
41+
)
42+
}
43+
44+
return null
45+
}

site/src/components/DeploySettingsLayout/Sidebar.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import LockRounded from "@mui/icons-material/LockOutlined"
66
import Globe from "@mui/icons-material/PublicOutlined"
77
import HubOutlinedIcon from "@mui/icons-material/HubOutlined"
88
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined"
9+
import MonitorHeartOutlined from "@mui/icons-material/MonitorHeartOutlined"
910
import { GitIcon } from "components/Icons/GitIcon"
1011
import { Stack } from "components/Stack/Stack"
1112
import { ElementType, PropsWithChildren, ReactNode, FC } from "react"
@@ -93,6 +94,14 @@ export const Sidebar: React.FC = () => {
9394
>
9495
Security
9596
</SidebarNavItem>
97+
{dashboard.experiments.includes("deployment_health_page") && (
98+
<SidebarNavItem
99+
href="/health"
100+
icon={<SidebarNavItemIcon icon={MonitorHeartOutlined} />}
101+
>
102+
Health
103+
</SidebarNavItem>
104+
)}
96105
</nav>
97106
)
98107
}

site/src/components/PageHeader/FullWidthPageHeader.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { makeStyles } from "@mui/styles"
22
import { FC, PropsWithChildren } from "react"
3+
import { combineClasses } from "utils/combineClasses"
34

4-
export const FullWidthPageHeader: FC<PropsWithChildren> = ({ children }) => {
5+
export const FullWidthPageHeader: FC<
6+
PropsWithChildren & { sticky?: boolean }
7+
> = ({ children, sticky = true }) => {
58
const styles = useStyles()
69

710
return (
8-
<header className={styles.header} data-testid="header">
11+
<header
12+
className={combineClasses([styles.header, sticky ? styles.sticky : ""])}
13+
data-testid="header"
14+
>
915
{children}
1016
</header>
1117
)
@@ -35,8 +41,7 @@ const useStyles = makeStyles((theme) => ({
3541
display: "flex",
3642
alignItems: "center",
3743
gap: theme.spacing(6),
38-
position: "sticky",
39-
top: 0,
44+
4045
zIndex: 10,
4146
flexWrap: "wrap",
4247

@@ -48,6 +53,10 @@ const useStyles = makeStyles((theme) => ({
4853
flexDirection: "column",
4954
},
5055
},
56+
sticky: {
57+
position: "sticky",
58+
top: 0,
59+
},
5160
actions: {
5261
marginLeft: "auto",
5362
[theme.breakpoints.down("md")]: {

site/src/components/Stats/Stats.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import Box from "@mui/material/Box"
12
import { makeStyles } from "@mui/styles"
23
import { ComponentProps, FC, PropsWithChildren } from "react"
34
import { combineClasses } from "utils/combineClasses"
45

5-
export const Stats: FC<ComponentProps<"div">> = (props) => {
6+
export const Stats: FC<ComponentProps<typeof Box>> = (props) => {
67
const styles = useStyles()
78
return (
8-
<div
9+
<Box
910
{...props}
1011
className={combineClasses([styles.stats, props.className])}
1112
/>
@@ -16,18 +17,18 @@ export const StatsItem: FC<
1617
{
1718
label: string
1819
value: string | number | JSX.Element
19-
} & ComponentProps<"div">
20+
} & ComponentProps<typeof Box>
2021
> = ({ label, value, ...divProps }) => {
2122
const styles = useStyles()
2223

2324
return (
24-
<div
25+
<Box
2526
{...divProps}
2627
className={combineClasses([styles.statItem, divProps.className])}
2728
>
2829
<span className={styles.statsLabel}>{label}:</span>
2930
<span className={styles.statsValue}>{value}</span>
30-
</div>
31+
</Box>
3132
)
3233
}
3334

site/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC } from "react"
1+
import { ComponentProps, FC } from "react"
22
import Editor, { DiffEditor, loader } from "@monaco-editor/react"
33
import * as monaco from "monaco-editor"
44
import { useCoderTheme } from "./coderTheme"
@@ -9,8 +9,10 @@ loader.config({ monaco })
99
export const SyntaxHighlighter: FC<{
1010
value: string
1111
language: string
12+
editorProps?: ComponentProps<typeof Editor> &
13+
ComponentProps<typeof DiffEditor>
1214
compareWith?: string
13-
}> = ({ value, compareWith, language }) => {
15+
}> = ({ value, compareWith, language, editorProps }) => {
1416
const styles = useStyles()
1517
const hasDiff = compareWith && value !== compareWith
1618
const coderTheme = useCoderTheme()
@@ -25,6 +27,7 @@ export const SyntaxHighlighter: FC<{
2527
renderSideBySide: true,
2628
readOnly: true,
2729
},
30+
...editorProps,
2831
}
2932

3033
if (coderTheme.isLoading) {
@@ -46,5 +49,6 @@ const useStyles = makeStyles((theme) => ({
4649
wrapper: {
4750
padding: theme.spacing(1, 0),
4851
background: theme.palette.background.paper,
52+
height: "100%",
4953
},
5054
}))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Meta, StoryObj } from "@storybook/react"
2+
import { HealthPageView } from "./HealthPage"
3+
import { MockHealth } from "testHelpers/entities"
4+
5+
const meta: Meta<typeof HealthPageView> = {
6+
title: "pages/HealthPageView",
7+
component: HealthPageView,
8+
args: {
9+
tab: {
10+
value: "derp",
11+
set: () => {},
12+
},
13+
healthStatus: MockHealth,
14+
},
15+
}
16+
17+
export default meta
18+
type Story = StoryObj<typeof HealthPageView>
19+
20+
export const HealthPage: Story = {}
21+
22+
export const UnhealthPage: Story = {
23+
args: {
24+
healthStatus: {
25+
...MockHealth,
26+
healthy: false,
27+
derp: {
28+
...MockHealth.derp,
29+
healthy: false,
30+
},
31+
},
32+
},
33+
}

0 commit comments

Comments
 (0)