8000 feat: show tags for psk provisioners by aslilac · Pull Request #14628 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: show tags for psk provisioners #14628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 17, 2024
Merged
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
work
  • Loading branch information
aslilac committed Sep 5, 2024
commit 665c9b317980588a68a32cd572e502cb830ee783
183 changes: 183 additions & 0 deletions site/src/modules/provisioners/ProvisionerGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useTheme } from "@emotion/react";
import Business from "@mui/icons-material/Business";
import Person from "@mui/icons-material/Person";
import Tooltip from "@mui/material/Tooltip";
import type { HealthMessage, ProvisionerDaemon } from "api/typesGenerated";
import { Pill } from "components/Pill/Pill";
import type { FC } from "react";
import { createDayString } from "utils/createDayString";
import { ProvisionerTag } from "./ProvisionerTag";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { buildInfo } from "api/queries/buildInfo";
import { useQuery } from "react-query";
import ArrowDownward from "@mui/icons-material/ArrowDownward";

type ProvisionerGroupType = "builtin" | "psk" | "key";

interface ProvisionerGroupProps {
readonly keyName?: string;
readonly type: ProvisionerGroupType;
readonly provisioners: ProvisionerDaemon[];
readonly warnings?: readonly HealthMessage[];
}

export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
keyName,
type,
provisioners,
warnings,
}) => {
const { metadata } = useEmbeddedMetadata();
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));

const [provisioner] = provisioners;
const theme = useTheme();
const daemonScope = provisioner.tags.scope || "organization";
const iconScope = daemonScope === "organization" ? <Business /> : <Person />;

const provisionerVersion = provisioner.version;
const allProvisionersAreSameVersion = provisioners.every(
(provisioner) => provisioner.version === provisionerVersion,
);
const upToDate =
allProvisionersAreSameVersion && buildInfoQuery.data?.version;
const protocolUpToDate =
allProvisionersAreSameVersion &&
buildInfoQuery?.data?.provisioner_api_version === provisioner.api_version;
const provisionerCount =
provisioners.length === 1
? "1 provisioner"
: `${provisioners.length} provisioners`;

const extraTags = Object.entries(provisioner.tags).filter(
([key]) => key !== "scope" && key !== "owner",
);
const isWarning = warnings && warnings.length > 0;
return (
<div
css={[
{
borderRadius: 8,
border: `1px solid ${theme.palette.divider}`,
fontSize: 14,
},
isWarning && { borderColor: theme.roles.warning.outline },
]}
>
<header
css={{
padding: 24,
display: "flex",
alignItems: "center",
justifyContenxt: "space-between",
gap: 24,
}}
>
<div
css={{
display: "flex",
alignItems: "center",
gap: 24,
objectFit: "fill",
}}
>
{type === "builtin" && (
<div css={{ lineHeight: "160%" }}>
<h4 css={{ fontWeight: 500, margin: 0 }}>
Built-in provisioners
</h4>
<span css={{ color: theme.palette.text.secondary }}>
{provisionerCount} &mdash; Built-in
</span>
</div>
)}
{type === "psk" && (
<div css={{ lineHeight: "160%" }}>
<h4 css={{ fontWeight: 500, margin: 0 }}>PSK provisioners</h4>
<span css={{ color: theme.palette.text.secondary }}>
{provisionerCount} &mdash;{" "}
{allProvisionersAreSameVersion ? (
<code>{provisionerVersion}</code>
) : (
<span>Multiple versions</span>
)}
</span>
</div>
)}
{type === "key" && (
<div css={{ lineHeight: "160%" }}>
<h4 css={{ fontWeight: 500, margin: 0 }}>
Key group &ndash; {keyName}
</h4>
<span css={{ color: theme.palette.text.secondary }}>
{provisionerCount} &mdash;{" "}
{allProvisionersAreSameVersion ? (
<code>{provisionerVersion}</code>
) : (
<span>Multiple versions</span>
)}
</span>
</div>
)}
</div>
<div
css={{
marginLeft: "auto",
display: "flex",
flexWrap: "wrap",
gap: 12,
}}
>
<Tooltip title="Scope">
<Pill size="lg" icon={iconScope}>
<span
css={{
":first-letter": { textTransform: "uppercase" },
}}
>
{daemonScope}
</span>
</Pill>
</Tooltip>
{type === "key" &&
extraTags.map(([key, value]) => (
<ProvisionerTag key={key} tagName={key} tagValue={value} />
))}
</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 && warnings.length > 0 ? (
<div css={{ display: "flex", flexDirection: "column" }}>
{warnings.map((warning) => (
<span key={warning.code}>{warning.message}</span>
))}
</div>
) : (
<span>No warnings</span>
)}
<span
css={{
display: "flex",
alignItems: "center",
gap: 4,
color: theme.roles.info.text,
}}
>
Show provisioner details{" "}
<ArrowDownward fontSize="small" color="inherit" />
</span>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
organizationsPermissions,
provisionerDaemons,
} from "api/queries/organizations";
import type { Organization } from "api/typesGenerated";
import type { Organization, ProvisionerDaemon } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Loader } from "components/Loader/Loader";
Expand All @@ -13,6 +13,39 @@ import { useParams } from "react-router-dom";
import { useOrganizationSettings } from "./ManagementSettingsLayout";
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";

export interface ProvisionersByGroup {
builtin: ProvisionerDaemon[];
psk: ProvisionerDaemon[];
keys: Map<string, ProvisionerDaemon[]>;
}

function groupProvisioners(
provisioners: readonly ProvisionerDaemon[],
): ProvisionersByGroup {
const groups: ProvisionersByGroup = { builtin: [], psk: [], keys: new Map() };
const type = "builtin";
const keyName = "TODO";

for (const it of provisioners) {
if (type === "builtin") {
groups.builtin.push(it);
continue;
}
if (type === "psk") {
groups.psk.push(it);
continue;
}

const keyGroup = groups.keys.get(keyName) ?? [];
if (!groups.keys.has(keyName)) {
groups.keys.set(keyName, keyGroup);
}
keyGroup.push(it);
}

return groups;
}

const OrganizationProvisionersPage: FC = () => {
const { organization: organizationName } = useParams() as {
organization: string;
Expand Down Expand Up @@ -54,7 +87,11 @@ const OrganizationProvisionersPage: FC = () => {
return <NotFoundPage />;
}

return <OrganizationProvisionersPageView provisioners={provisioners} />;
return (
<OrganizationProvisionersPageView
provisioners={groupProvisioners(provisioners)}
/>
);
};

export default OrganizationProvisionersPage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,37 @@ type Story = StoryObj<typeof OrganizationProvisionersPageView>;

export const Provisioners: Story = {
args: {
provisioners: [
MockProvisioner,
MockUserProvisioner,
{
...MockProvisioner,
tags: {
...MockProvisioner.tags,
都市: "ユタ",
きっぷ: "yes",
ちいさい: "no",
provisioners: {
builtin: [MockProvisioner, MockProvisioner],
psk: [
MockProvisioner,
MockUserProvisioner,
{
...MockProvisioner,
tags: {
...MockProvisioner.tags,
都市: "ユタ",
きっぷ: "yes",
ちいさい: "no",
},
},
9E81 },
],
],
keys: new Map([
[
"ケイラ",
[
{
...MockProvisioner,
tags: {
...MockProvisioner.tags,
都市: "ユタ",
きっぷ: "yes",
ちいさい: "no",
},
},
],
],
]),
},
},
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import Button from "@mui/material/Button";
import type { ProvisionerDaemon } from "api/typesGenerated";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { Stack } from "components/Stack/Stack";
import { Provisioner } from "modules/provisioners/Provisioner";
import type { FC } from "react";
import { docs } from "utils/docs";
import type { ProvisionersByGroup } from "./OrganizationProvisionersPage";
import { ProvisionerGroup } from "modules/provisioners/ProvisionerGroup";

interface OrganizationProvisionersPageViewProps {
provisioners: ProvisionerDaemon[];
provisioners: ProvisionersByGroup;
}

export const OrganizationProvisionersPageView: FC<
Expand All @@ -32,8 +33,22 @@ export const OrganizationProvisionersPageView: FC<
<PageHeaderTitle>Provisioners</PageHeaderTitle>
</PageHeader>
<Stack spacing={4.5}>
{provisioners.map((provisioner) => (
<Provisioner key={provisioner.id} provisioner={provisioner} />
{provisioners.builtin.length > 0 && (
<ProvisionerGroup
type="builtin"
provisioners={provisioners.builtin}
/>
)}
{provisioners.psk.length > 0 && (
<ProvisionerGroup type="psk" provisioners={provisioners.psk} />
)}
{[...provisioners.keys].map(([keyId, provisioners]) => (
<ProvisionerGroup
key={keyId}
keyName={keyId}
type="key"
provisioners={provisioners}
/>
))}
</Stack>
</div>
Expand Down
Loading
0