Skip to content

Commit 4732f08

Browse files
authored
fix: show an error when a user doesn't have permission to view the health page (#16580)
1 parent 2c6df5a commit 4732f08

File tree

1 file changed

+149
-134
lines changed

1 file changed

+149
-134
lines changed

site/src/pages/HealthPage/HealthLayout.tsx

Lines changed: 149 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import IconButton from "@mui/material/IconButton";
77
import Tooltip from "@mui/material/Tooltip";
88
import { health, refreshHealth } from "api/queries/debug";
99
import type { HealthSeverity } from "api/typesGenerated";
10+
import { ErrorAlert } from "components/Alert/ErrorAlert";
1011
import { Loader } from "components/Loader/Loader";
1112
import { type ClassName, useClassName } from "hooks/useClassName";
1213
import kebabCase from "lodash/fp/kebabCase";
@@ -22,7 +23,11 @@ import { HealthIcon } from "./Content";
2223
export const HealthLayout: FC = () => {
2324
const theme = useTheme();
2425
const queryClient = useQueryClient();
25-
const { data: healthStatus } = useQuery({
26+
const {
27+
data: healthStatus,
28+
isLoading,
29+
error,
30+
} = useQuery({
2631
...health(),
2732
refetchInterval: 30_000,
2833
});
@@ -42,161 +47,171 @@ export const HealthLayout: FC = () => {
4247
const link = useClassName(classNames.link, []);
4348
const activeLink = useClassName(classNames.activeLink, []);
4449

50+
if (isLoading || !healthStatus) {
51+
return (
52+
<div className="p-6">
53+
<Loader />
54+
</div>
55+
);
56+
}
57+
58+
if (error) {
59+
return (
60+
<div className="p-6">
61+
<ErrorAlert error={error} />
62+
</div>
63+
);
64+
}
65+
4566
return (
4667
<>
4768
<Helmet>
4869
<title>{pageTitle("Health")}</title>
4970
</Helmet>
5071

51-
{healthStatus ? (
52-
<DashboardFullPage>
72+
<DashboardFullPage>
73+
<div
74+
css={{
75+
display: "flex",
76+
flexBasis: 0,
77+
flex: 1,
78+
overflow: "hidden",
79+
}}
80+
>
5381
<div
5482
css={{
55-
display: "flex",
56-
flexBasis: 0,
57-
flex: 1,
58-
overflow: "hidden",
83+
width: 256,
84+
flexShrink: 0,
85+
borderRight: `1px solid ${theme.palette.divider}`,
86+
fontSize: 14,
5987
}}
6088
>
6189
<div
6290
css={{
63-
width: 256,
64-
flexShrink: 0,
65-
borderRight: `1px solid ${theme.palette.divider}`,
66-
fontSize: 14,
91+
padding: 24,
92+
display: "flex",
93+
flexDirection: "column",
94+
gap: 16,
6795
}}
6896
>
69-
<div
70-
css={{
71-
padding: 24,
72-
display: "flex",
73-
flexDirection: "column",
74-
gap: 16,
75-
}}
76-
>
77-
<div>
78-
<div
79-
css={{
80-
display: "flex",
81-
alignItems: "center",
82-
justifyContent: "space-between",
83-
}}
84-
>
85-
<HealthIcon size={32} severity={healthStatus.severity} />
86-
87-
<Tooltip title="Refresh health checks">
88-
<IconButton
89-
size="small"
90-
disabled={isRefreshing}
91-
data-testid="healthcheck-refresh-button"
92-
onClick={() => {
93-
forceRefresh();
94-
}}
95-
>
96-
{isRefreshing ? (
97-
<CircularProgress size={16} />
98-
) : (
99-
<ReplayIcon css={{ width: 20, height: 20 }} />
100-
)}
101-
</IconButton>
102-
</Tooltip>
103-
</div>
104-
<div css={{ fontWeight: 500, marginTop: 16 }}>
105-
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
106-
</div>
107-
<div
108-
css={{
109-
color: theme.palette.text.secondary,
110-
lineHeight: "150%",
111-
}}
112-
>
113-
{healthStatus.healthy
114-
? Object.keys(visibleSections).some((key) => {
115-
const section =
116-
healthStatus[key as keyof typeof visibleSections];
117-
return (
118-
section.warnings && section.warnings.length > 0
119-
);
120-
})
121-
? "All systems operational, but performance might be degraded"
122-
: "All systems operational"
123-
: "Some issues have been detected"}
124-
</div>
125-
</div>
97+
<div>
98+
<div
99+
css={{
100+
display: "flex",
101+
alignItems: "center",
102+
justifyContent: "space-between",
103+
}}
104+
>
105+
<HealthIcon size={32} severity={healthStatus.severity} />
126106

127-
<div css={{ display: "flex", flexDirection: "column" }}>
128-
<span css={{ fontWeight: 500 }}>Last check</span>
129-
<span
130-
data-chromatic="ignore"
131-
css={{
132-
color: theme.palette.text.secondary,
133-
lineHeight: "150%",
134-
}}
135-
>
136-
{createDayString(healthStatus.time)}
137-
</span>
107+
<Tooltip title="Refresh health checks">
108+
<IconButton
109+
size="small"
110+
disabled={isRefreshing}
111+
data-testid="healthcheck-refresh-button"
112+
onClick={() => {
113+
forceRefresh();
114+
}}
115+
>
116+
{isRefreshing ? (
117+
<CircularProgress size={16} />
118+
) : (
119+
<ReplayIcon css={{ width: 20, height: 20 }} />
120+
)}
121+
</IconButton>
122+
</Tooltip>
138123
</div>
139-
140-
<div css={{ display: "flex", flexDirection: "column" }}>
141-
<span css={{ fontWeight: 500 }}>Version</span>
142-
<span
143-
data-chromatic="ignore"
144-
css={{
145-
color: theme.palette.text.secondary,
146-
lineHeight: "150%",
147-
}}
148-
>
149-
{healthStatus.coder_version}
150-
</span>
124+
<div css={{ fontWeight: 500, marginTop: 16 }}>
125+
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
126+
</div>
127+
<div
128+
css={{
129+
color: theme.palette.text.secondary,
130+
lineHeight: "150%",
131+
}}
132+
>
133+
{healthStatus.healthy
134+
? Object.keys(visibleSections).some((key) => {
135+
const section =
136+
healthStatus[key as keyof typeof visibleSections];
137+
return section.warnings && section.warnings.length > 0;
138+
})
139+
? "All systems operational, but performance might be degraded"
140+
: "All systems operational"
141+
: "Some issues have been detected"}
151142
</div>
152143
</div>
153144

154-
<nav css={{ display: "flex", flexDirection: "column", gap: 1 }}>
155-
{Object.entries(visibleSections)
156-
.sort()
157-
.map(([key, label]) => {
158-
const healthSection =
159-
healthStatus[key as keyof typeof visibleSections];
160-
161-
return (
162-
<NavLink
163-
end
164-
key={key}
165-
to={`/health/${kebabCase(key)}`}
166-
className={({ isActive }) =>
167-
cx([link, isActive && activeLink])
168-
}
169-
>
170-
<HealthIcon
171-
size={16}
172-
severity={healthSection.severity as HealthSeverity}
173-
/>
174-
{label}
175-
{healthSection.dismissed && (
176-
<NotificationsOffOutlined
177-
css={{
178-
fontSize: 14,
179-
marginLeft: "auto",
180-
color: theme.palette.text.disabled,
181-
}}
182-
/>
183-
)}
184-
</NavLink>
185-
);
186-
})}
187-
</nav>
188-
</div>
145+
<div css={{ display: "flex", flexDirection: "column" }}>
146+
<span css={{ fontWeight: 500 }}>Last check</span>
147+
<span
148+
data-chromatic="ignore"
149+
css={{
150+
color: theme.palette.text.secondary,
151+
lineHeight: "150%",
152+
}}
153+
>
154+
{createDayString(healthStatus.time)}
155+
</span>
156+
</div>
189157

190-
<div css={{ overflowY: "auto", width: "100%" }}>
191-
<Suspense fallback={<Loader />}>
192-
<Outlet context={healthStatus} />
193-
</Suspense>
158+
<div css={{ display: "flex", flexDirection: "column" }}>
159+
<span css={{ fontWeight: 500 }}>Version</span>
160+
<span
161+
data-chromatic="ignore"
162+
css={{
163+
color: theme.palette.text.secondary,
164+
lineHeight: "150%",
165+
}}
166+
>
167+
{healthStatus.coder_version}
168+
</span>
169+
</div>
194170
</div>
171+
172+
<nav css={{ display: "flex", flexDirection: "column", gap: 1 }}>
173+
{Object.entries(visibleSections)
174+
.sort()
175+
.map(([key, label]) => {
176+
const healthSection =
177+
healthStatus[key as keyof typeof visibleSections];
178+
179+
return (
180+
<NavLink
181+
end
182+
key={key}
183+
to={`/health/${kebabCase(key)}`}
184+
className={({ isActive }) =>
185+
cx([link, isActive && activeLink])
186+
}
187+
>
188+
<HealthIcon
189+
size={16}
190+
severity={healthSection.severity as HealthSeverity}
191+
/>
192+
{label}
193+
{healthSection.dismissed && (
194+
<NotificationsOffOutlined
195+
css={{
196+
fontSize: 14,
197+
marginLeft: "auto",
198+
color: theme.palette.text.disabled,
199+
}}
200+
/>
201+
)}
202+
</NavLink>
203+
);
204+
})}
205+
</nav>
195206
</div>
196-
</DashboardFullPage>
197-
) : (
198-
<Loader />
199-
)}
207+
208+
<div css={{ overflowY: "auto", width: "100%" }}>
209+
<Suspense fallback={<Loader />}>
210+
<Outlet context={healthStatus} />
211+
</Suspense>
212+
</div>
213+
</div>
214+
</DashboardFullPage>
200215
</>
201216
);
202217
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy