fix: show an error when a user doesn't have permission to view the health page (#16580)

This commit is contained in:
brettkolodny
2025-02-19 10:54:35 -05:00
committed by GitHub
parent 2c6df5a9ae
commit 4732f08588
+148 -133
View File
@@ -7,6 +7,7 @@ import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { health, refreshHealth } from "api/queries/debug";
import type { HealthSeverity } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader";
import { type ClassName, useClassName } from "hooks/useClassName";
import kebabCase from "lodash/fp/kebabCase";
@@ -22,7 +23,11 @@ import { HealthIcon } from "./Content";
export const HealthLayout: FC = () => {
const theme = useTheme();
const queryClient = useQueryClient();
const { data: healthStatus } = useQuery({
const {
data: healthStatus,
isLoading,
error,
} = useQuery({
...health(),
refetchInterval: 30_000,
});
@@ -42,161 +47,171 @@ export const HealthLayout: FC = () => {
const link = useClassName(classNames.link, []);
const activeLink = useClassName(classNames.activeLink, []);
if (isLoading || !healthStatus) {
return (
<div className="p-6">
<Loader />
</div>
);
}
if (error) {
return (
<div className="p-6">
<ErrorAlert error={error} />
</div>
);
}
return (
<>
<Helmet>
<title>{pageTitle("Health")}</title>
</Helmet>
{healthStatus ? (
<DashboardFullPage>
<DashboardFullPage>
<div
css={{
display: "flex",
flexBasis: 0,
flex: 1,
overflow: "hidden",
}}
>
<div
css={{
display: "flex",
flexBasis: 0,
flex: 1,
overflow: "hidden",
width: 256,
flexShrink: 0,
borderRight: `1px solid ${theme.palette.divider}`,
fontSize: 14,
}}
>
<div
css={{
width: 256,
flexShrink: 0,
borderRight: `1px solid ${theme.palette.divider}`,
fontSize: 14,
padding: 24,
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<div
css={{
padding: 24,
display: "flex",
flexDirection: "column",
gap: 16,
}}
>
<div>
<div
css={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<HealthIcon size={32} severity={healthStatus.severity} />
<div>
<div
css={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<HealthIcon size={32} severity={healthStatus.severity} />
<Tooltip title="Refresh health checks">
<IconButton
size="small"
disabled={isRefreshing}
data-testid="healthcheck-refresh-button"
onClick={() => {
forceRefresh();
}}
>
{isRefreshing ? (
<CircularProgress size={16} />
) : (
<ReplayIcon css={{ width: 20, height: 20 }} />
)}
</IconButton>
</Tooltip>
</div>
<div css={{ fontWeight: 500, marginTop: 16 }}>
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
</div>
<div
css={{
color: theme.palette.text.secondary,
lineHeight: "150%",
}}
>
{healthStatus.healthy
? Object.keys(visibleSections).some((key) => {
const section =
healthStatus[key as keyof typeof visibleSections];
return (
section.warnings && section.warnings.length > 0
);
})
? "All systems operational, but performance might be degraded"
: "All systems operational"
: "Some issues have been detected"}
</div>
<Tooltip title="Refresh health checks">
<IconButton
size="small"
disabled={isRefreshing}
data-testid="healthcheck-refresh-button"
onClick={() => {
forceRefresh();
}}
>
{isRefreshing ? (
<CircularProgress size={16} />
) : (
<ReplayIcon css={{ width: 20, height: 20 }} />
)}
</IconButton>
</Tooltip>
</div>
<div css={{ display: "flex", flexDirection: "column" }}>
<span css={{ fontWeight: 500 }}>Last check</span>
<span
data-chromatic="ignore"
css={{
color: theme.palette.text.secondary,
lineHeight: "150%",
}}
>
{createDayString(healthStatus.time)}
</span>
<div css={{ fontWeight: 500, marginTop: 16 }}>
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
</div>
<div css={{ display: "flex", flexDirection: "column" }}>
<span css={{ fontWeight: 500 }}>Version</span>
<span
data-chromatic="ignore"
css={{
color: theme.palette.text.secondary,
lineHeight: "150%",
}}
>
{healthStatus.coder_version}
</span>
<div
css={{
color: theme.palette.text.secondary,
lineHeight: "150%",
}}
>
{healthStatus.healthy
? Object.keys(visibleSections).some((key) => {
const section =
healthStatus[key as keyof typeof visibleSections];
return section.warnings && section.warnings.length > 0;
})
? "All systems operational, but performance might be degraded"
: "All systems operational"
: "Some issues have been detected"}
</div>
</div>
<nav css={{ display: "flex", flexDirection: "column", gap: 1 }}>
{Object.entries(visibleSections)
.sort()
.map(([key, label]) => {
const healthSection =
healthStatus[key as keyof typeof visibleSections];
<div css={{ display: "flex", flexDirection: "column" }}>
<span css={{ fontWeight: 500 }}>Last check</span>
<span
data-chromatic="ignore"
css={{
color: theme.palette.text.secondary,
lineHeight: "150%",
}}
>
{createDayString(healthStatus.time)}
</span>
</div>
return (
<NavLink
end
key={key}
to={`/health/${kebabCase(key)}`}
className={({ isActive }) =>
cx([link, isActive && activeLink])
}
>
<HealthIcon
size={16}
severity={healthSection.severity as HealthSeverity}
<div css={{ display: "flex", flexDirection: "column" }}>
<span css={{ fontWeight: 500 }}>Version</span>
<span
data-chromatic="ignore"
css={{
color: theme.palette.text.secondary,
lineHeight: "150%",
}}
>
{healthStatus.coder_version}
</span>
</div>
</div>
<nav css={{ display: "flex", flexDirection: "column", gap: 1 }}>
{Object.entries(visibleSections)
.sort()
.map(([key, label]) => {
const healthSection =
healthStatus[key as keyof typeof visibleSections];
return (
<NavLink
end
key={key}
to={`/health/${kebabCase(key)}`}
className={({ isActive }) =>
cx([link, isActive && activeLink])
}
>
<HealthIcon
size={16}
severity={healthSection.severity as HealthSeverity}
/>
{label}
{healthSection.dismissed && (
<NotificationsOffOutlined
css={{
fontSize: 14,
marginLeft: "auto",
color: theme.palette.text.disabled,
}}
/>
{label}
{healthSection.dismissed && (
<NotificationsOffOutlined
css={{
fontSize: 14,
marginLeft: "auto",
color: theme.palette.text.disabled,
}}
/>
)}
</NavLink>
);
})}
</nav>
</div>
<div css={{ overflowY: "auto", width: "100%" }}>
<Suspense fallback={<Loader />}>
<Outlet context={healthStatus} />
</Suspense>
</div>
)}
</NavLink>
);
})}
</nav>
</div>
</DashboardFullPage>
) : (
<Loader />
)}
<div css={{ overflowY: "auto", width: "100%" }}>
<Suspense fallback={<Loader />}>
<Outlet context={healthStatus} />
</Suspense>
</div>
</div>
</DashboardFullPage>
</>
);
};