feat(site): add healthcheck page for provisioner daemons (#11494)

Part of #10676

- Adds a health section for provisioner daemons (mostly cannibalized from the Workspace Proxy section)
- Adds a corresponding storybook entry for provisioner daemons health section
- Fixed an issue where dismissing the provisioner daemons warnings would result in a 500 error
- Adds provisioner daemon error codes to docs
This commit is contained in:
Cian Johnston
2024-01-08 17:14:09 +00:00
committed by GitHub
parent 6096af77c8
commit 220e95dd5c
8 changed files with 278 additions and 4 deletions
+7
View File
@@ -234,6 +234,9 @@ const WebsocketPage = lazy(() => import("./pages/HealthPage/WebsocketPage"));
const WorkspaceProxyHealthPage = lazy(
() => import("./pages/HealthPage/WorkspaceProxyPage"),
);
const ProvisionerDaemonsHealthPage = lazy(
() => import("./pages/HealthPage/ProvisionerDaemonsPage"),
);
export const AppRouter: FC = () => {
return (
@@ -400,6 +403,10 @@ export const AppRouter: FC = () => {
path="workspace-proxy"
element={<WorkspaceProxyHealthPage />}
/>
<Route
path="provisioner-daemons"
element={<ProvisionerDaemonsHealthPage />}
/>
</Route>
{/* Using path="*"" means "match anything", so this route
acts like a catch-all for URLs that we don't have explicit
@@ -34,6 +34,7 @@ export function HealthLayout() {
websocket: "Websocket",
database: "Database",
workspace_proxy: "Workspace Proxy",
provisioner_daemons: "Provisioner Daemons",
} as const;
const visibleSections = filterVisibleSections(sections);
@@ -0,0 +1,16 @@
import { StoryObj, Meta } from "@storybook/react";
import { ProvisionerDaemonsPage } from "./ProvisionerDaemonsPage";
import { generateMeta } from "./storybook";
const meta: Meta = {
title: "pages/Health/ProvisionerDaemons",
...generateMeta({
path: "/health/provisioner-daemons",
element: <ProvisionerDaemonsPage />,
}),
};
export default meta;
type Story = StoryObj;
export const Default: Story = {};
@@ -0,0 +1,166 @@
import { Header, HeaderTitle, HealthyDot, Main, Pill } from "./Content";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { useTheme } from "@mui/material/styles";
import { DismissWarningButton } from "./DismissWarningButton";
import { Alert } from "components/Alert/Alert";
import { HealthcheckReport } from "api/typesGenerated";
import { createDayString } from "utils/createDayString";
import { useOutletContext } from "react-router-dom";
import Business from "@mui/icons-material/Business";
import Person from "@mui/icons-material/Person";
import SwapHoriz from "@mui/icons-material/SwapHoriz";
import Tooltip from "@mui/material/Tooltip";
import Sell from "@mui/icons-material/Sell";
export const ProvisionerDaemonsPage = () => {
const healthStatus = useOutletContext<HealthcheckReport>();
const { provisioner_daemons: daemons } = healthStatus;
const theme = useTheme();
return (
<>
<Helmet>
<title>{pageTitle("Provisioner Daemons - Health")}</title>
</Helmet>
<Header>
<HeaderTitle>
<HealthyDot severity={daemons.severity} />
Provisioner Daemons
</HeaderTitle>
<DismissWarningButton healthcheck="ProvisionerDaemons" />
</Header>
<Main>
{daemons.warnings.map((warning) => {
return (
<Alert key={warning.code} severity="warning">
{warning.message}
</Alert>
);
})}
{daemons.items.map(({ provisioner_daemon: daemon, warnings }) => {
const daemonScope = daemon.tags["scope"] || "organization";
const iconScope =
daemonScope === "organization" ? <Business /> : <Person />;
const extraTags = Object.keys(daemon.tags)
.filter((key) => key !== "scope" && key !== "owner")
.reduce(
(acc, key) => {
acc[key] = daemon.tags[key];
return acc;
},
{} as Record<string, string>,
);
const isWarning = warnings.length > 0;
return (
<div
key={daemon.name}
css={{
borderRadius: 8,
border: `1px solid ${
isWarning
? theme.palette.warning.light
: theme.palette.divider
}`,
fontSize: 14,
}}
>
<header
css={{
padding: 24,
display: "flex",
alignItems: "center",
justifyContenxt: "space-between",
gap: 24,
}}
>
<div
css={{
display: "flex",
alignItems: "center",
gap: 24,
objectFit: "fill",
}}
>
<div css={{ lineHeight: "160%" }}>
<h4 css={{ fontWeight: 500, margin: 0 }}>{daemon.name}</h4>
<span css={{ color: theme.palette.text.secondary }}>
<code>{daemon.version}</code>
</span>
</div>
</div>
<div
css={{
marginLeft: "auto",
display: "flex",
flexWrap: "wrap",
gap: 12,
}}
>
<Tooltip title="API Version">
<Pill icon={<SwapHoriz />}>
<code>{daemon.api_version}</code>
</Pill>
</Tooltip>
<Tooltip title="Scope">
<Pill icon={iconScope}>
<span
css={{
":first-letter": { textTransform: "uppercase" },
}}
>
{daemonScope}
</span>
</Pill>
</Tooltip>
{Object.keys(extraTags).map((k) => (
<Tooltip key={k} title={k}>
<Pill key={k} icon={<Sell />}>
{extraTags[k]}
</Pill>
</Tooltip>
))}
</div>
</header>
<div
css={{
borderTop: `1px solid ${theme.palette.divider}`,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "8px 24px",
fontSize: 12,
color: theme.palette.text.secondary,
}}
>
{warnings.length > 0 ? (
<div css={{ display: "flex", flexDirection: "column" }}>
{warnings.map((warning, i) => (
<span key={i}>{warning.message}</span>
))}
</div>
) : (
<span>No warnings</span>
)}
{daemon.last_seen_at && (
<span
css={{ color: theme.palette.text.secondary }}
data-chromatic="ignore"
>
Last seen {createDayString(daemon.last_seen_at)}
</span>
)}
</div>
</div>
);
})}
</Main>
</>
);
};
export default ProvisionerDaemonsPage;
+39 -4
View File
@@ -3103,7 +3103,16 @@ export const MockHealth: TypesGen.HealthcheckReport = {
},
provisioner_daemons: {
severity: "ok",
warnings: [],
warnings: [
{
message: "Something is wrong!",
code: "EUNKNOWN",
},
{
message: "This is also bad.",
code: "EPD01",
},
],
dismissed: false,
items: [
{
@@ -3111,16 +3120,42 @@ export const MockHealth: TypesGen.HealthcheckReport = {
id: "e455b582-ac04-4323-9ad6-ab71301fa006",
created_at: "2024-01-04T15:53:03.21563Z",
last_seen_at: "2024-01-04T16:05:03.967551Z",
name: "vvuurrkk-2",
version: "v2.6.0-devel+965ad5e96",
name: "ok",
version: "v2.3.4-devel+abcd1234",
api_version: "1.0",
provisioners: ["echo", "terraform"],
tags: {
owner: "",
scope: "organization",
custom_tag_name: "custom_tag_value",
},
},
warnings: [],
},
{
provisioner_daemon: {
id: "e455b582-ac04-4323-9ad6-ab71301fa006",
created_at: "2024-01-04T15:53:03.21563Z",
last_seen_at: "2024-01-04T16:05:03.967551Z",
name: "unhappy",
version: "v0.0.1",
api_version: "0.1",
provisioners: ["echo", "terraform"],
tags: {
owner: "",
scope: "organization",
},
},
warnings: [],
warnings: [
{
message: "Something specific is wrong with this daemon.",
code: "EUNKNOWN",
},
{
message: "And now for something completely different.",
code: "EUNKNOWN",
},
],
},
],
},