mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): improve health check page sidebar (#10960)
This commit is contained in:
@@ -35,11 +35,10 @@ export const SyntaxHighlighter: FC<{
|
||||
return (
|
||||
<div
|
||||
data-chromatic="ignore"
|
||||
css={(theme) => ({
|
||||
css={{
|
||||
padding: "8px 0",
|
||||
background: theme.palette.background.paper,
|
||||
height: "100%",
|
||||
})}
|
||||
}}
|
||||
>
|
||||
{hasDiff ? (
|
||||
<DiffEditor original={compareWith} modified={value} {...commonProps} />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { getHealth } from "api/api";
|
||||
@@ -6,23 +5,17 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { useTab } from "hooks";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { colors } from "theme/colors";
|
||||
import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined";
|
||||
import ErrorOutline from "@mui/icons-material/ErrorOutline";
|
||||
import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
FullWidthPageHeader,
|
||||
PageHeaderTitle,
|
||||
PageHeaderSubtitle,
|
||||
} from "components/PageHeader/FullWidthPageHeader";
|
||||
import { Stats, StatsItem } from "components/Stats/Stats";
|
||||
import { createDayString } from "utils/createDayString";
|
||||
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import { FC } from "react";
|
||||
import { health, refreshHealth } from "api/queries/debug";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
|
||||
const sections = {
|
||||
derp: "DERP",
|
||||
@@ -74,62 +67,10 @@ export function HealthPageView({
|
||||
forceRefresh: () => void;
|
||||
isRefreshing: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<DashboardFullPage>
|
||||
<FullWidthPageHeader sticky={false}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
{healthStatus.healthy ? (
|
||||
<CheckCircleOutlined
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: (theme) => theme.palette.success.light,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ErrorOutline
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: (theme) => theme.palette.error.main,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<PageHeaderTitle>
|
||||
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
|
||||
</PageHeaderTitle>
|
||||
<PageHeaderSubtitle>
|
||||
{healthStatus.healthy
|
||||
? Object.keys(sections).some(
|
||||
(key) =>
|
||||
healthStatus[key as keyof typeof sections].warnings !==
|
||||
null &&
|
||||
healthStatus[key as keyof typeof sections].warnings
|
||||
.length > 0,
|
||||
)
|
||||
? "All systems operational, but performance might be degraded"
|
||||
: "All systems operational"
|
||||
: "Some issues have been detected"}
|
||||
</PageHeaderSubtitle>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<Stats aria-label="Deployment details" css={styles.stats}>
|
||||
<StatsItem
|
||||
css={styles.statsItem}
|
||||
label="Last check"
|
||||
value={createDayString(healthStatus.time)}
|
||||
/>
|
||||
<StatsItem
|
||||
css={styles.statsItem}
|
||||
label="Coder version"
|
||||
value={healthStatus.coder_version}
|
||||
/>
|
||||
</Stats>
|
||||
<RefreshButton loading={isRefreshing} handleAction={forceRefresh} />
|
||||
</FullWidthPageHeader>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -138,26 +79,112 @@ export function HealthPageView({
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
<div
|
||||
css={{
|
||||
width: 256,
|
||||
flexShrink: 0,
|
||||
borderRight: (theme) => `1px solid ${theme.palette.divider}`,
|
||||
borderRight: `1px solid ${theme.palette.divider}`,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: 10,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
color: (theme) => theme.palette.text.secondary,
|
||||
padding: "12px 24px",
|
||||
letterSpacing: "0.5px",
|
||||
<div
|
||||
css={{
|
||||
padding: 24,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 16,
|
||||
}}
|
||||
>
|
||||
Health
|
||||
</Box>
|
||||
<Box component="nav">
|
||||
<div>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
{healthStatus.healthy ? (
|
||||
<CheckCircleOutlined
|
||||
css={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: theme.palette.success.light,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ErrorOutline
|
||||
css={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: theme.palette.error.light,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<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(sections).some(
|
||||
(key) =>
|
||||
healthStatus[key as keyof typeof sections].warnings !==
|
||||
null &&
|
||||
healthStatus[key as keyof typeof sections].warnings
|
||||
.length > 0,
|
||||
)
|
||||
? "All systems operational, but performance might be degraded"
|
||||
: "All systems operational"
|
||||
: "Some issues have been detected"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
<span css={{ fontWeight: 500 }}>Last check</span>
|
||||
<span
|
||||
css={{
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "150%",
|
||||
}}
|
||||
>
|
||||
{createDayString(healthStatus.time)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
<span css={{ fontWeight: 500 }}>Version</span>
|
||||
<span
|
||||
css={{
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "150%",
|
||||
}}
|
||||
>
|
||||
{healthStatus.coder_version}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav css={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
{Object.keys(sections)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
@@ -168,68 +195,68 @@ export function HealthPageView({
|
||||
const isHealthy = healthSection.healthy;
|
||||
const isWarning = healthSection.warnings?.length > 0;
|
||||
return (
|
||||
<Box
|
||||
component="button"
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => {
|
||||
tab.set(key);
|
||||
}}
|
||||
sx={{
|
||||
background: isActive ? colors.gray[13] : "none",
|
||||
css={{
|
||||
background: isActive
|
||||
? theme.palette.action.hover
|
||||
: "none",
|
||||
border: "none",
|
||||
fontSize: 14,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
gap: 12,
|
||||
textAlign: "left",
|
||||
height: 36,
|
||||
padding: "0 24px",
|
||||
cursor: "pointer",
|
||||
pointerEvents: isActive ? "none" : "auto",
|
||||
color: (theme) =>
|
||||
isActive
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
color: isActive
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
"&:hover": {
|
||||
background: (theme) => theme.palette.action.hover,
|
||||
color: (theme) => theme.palette.text.primary,
|
||||
background: theme.palette.action.hover,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isHealthy ? (
|
||||
isWarning ? (
|
||||
<CheckCircleOutlined
|
||||
sx={{
|
||||
css={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: (theme) => theme.palette.warning.main,
|
||||
color: theme.palette.warning.light,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CheckCircleOutlined
|
||||
sx={{
|
||||
css={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: (theme) => theme.palette.success.light,
|
||||
color: theme.palette.success.light,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<ErrorOutline
|
||||
sx={{
|
||||
css={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: (theme) => theme.palette.error.main,
|
||||
color: theme.palette.error.main,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
</Box>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
</nav>
|
||||
</div>
|
||||
{/* 62px - navbar and 36px - the bottom bar */}
|
||||
<Box sx={{ overflowY: "auto", width: "100%" }} data-chromatic="ignore">
|
||||
<SyntaxHighlighter
|
||||
@@ -246,53 +273,3 @@ export function HealthPageView({
|
||||
</DashboardFullPage>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
stats: (theme) => ({
|
||||
padding: 0,
|
||||
border: 0,
|
||||
gap: 48,
|
||||
rowGap: 24,
|
||||
flex: 1,
|
||||
|
||||
[theme.breakpoints.down("md")]: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
gap: 8,
|
||||
},
|
||||
}),
|
||||
|
||||
statsItem: {
|
||||
flexDirection: "column",
|
||||
gap: 0,
|
||||
padding: 0,
|
||||
|
||||
"& > span:first-of-type": {
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
interface HealthcheckAction {
|
||||
handleAction: () => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const RefreshButton: FC<HealthcheckAction> = ({
|
||||
handleAction,
|
||||
loading,
|
||||
}) => {
|
||||
return (
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
loadingPosition="start"
|
||||
data-testid="healthcheck-refresh-button"
|
||||
startIcon={<ReplayIcon />}
|
||||
onClick={handleAction}
|
||||
>
|
||||
Refresh
|
||||
</LoadingButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,12 +80,12 @@ export let dark = createTheme({
|
||||
|
||||
body1: {
|
||||
fontSize: "1rem" /* 16px at default scaling */,
|
||||
lineHeight: "1.5rem" /* 24px at default scaling */,
|
||||
lineHeight: "160%",
|
||||
},
|
||||
|
||||
body2: {
|
||||
fontSize: "0.875rem" /* 14px at default scaling */,
|
||||
lineHeight: "1.25rem" /* 20px at default scaling */,
|
||||
lineHeight: "160%",
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
|
||||
Reference in New Issue
Block a user