-
Notifications
You must be signed in to change notification settings - Fork 899
feat: group provisioners by authentication method #14580
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import { useTheme } from "@emotion/react"; | ||
import Business from "@mui/icons-material/Business"; | ||
import Person from "@mui/icons-material/Person"; | ||
import Button from "@mui/material/Button"; | ||
import Tooltip from "@mui/material/Tooltip"; | ||
import type { BuildInfoResponse, ProvisionerDaemon } from "api/typesGenerated"; | ||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow"; | ||
import { Pill } from "components/Pill/Pill"; | ||
import { type FC, useState } from "react"; | ||
import { createDayString } from "utils/createDayString"; | ||
import { ProvisionerTag } from "./ProvisionerTag"; | ||
|
||
type ProvisionerGroupType = "builtin" | "psk" | "key"; | ||
|
||
interface ProvisionerGroupProps { | ||
readonly buildInfo?: BuildInfoResponse; | ||
readonly keyName?: string; | ||
readonly type: ProvisionerGroupType; | ||
readonly provisioners: ProvisionerDaemon[]; | ||
aslilac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({ | ||
buildInfo, | ||
keyName, | ||
type, | ||
provisioners, | ||
}) => { | ||
const [provisioner] = provisioners; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be a check to make sure that the If there are multiple provisioners, is there anything implied by the order they appear in? As in, is the first provisioner always the "main" one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. currently, it should never be passed an empty |
||
const theme = useTheme(); | ||
|
||
const [showDetails, setShowDetails] = useState(false); | ||
|
||
const daemonScope = provisioner.tags.scope || "organization"; | ||
const iconScope = daemonScope === "organization" ? <Business /> : <Person />; | ||
aslilac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const provisionerVersion = provisioner.version; | ||
const allProvisionersAreSameVersion = provisioners.every( | ||
(provisioner) => provisioner.version === provisionerVersion, | ||
); | ||
const upToDate = | ||
allProvisionersAreSameVersion && buildInfo?.version === provisioner.version; | ||
const provisionerCount = | ||
provisioners.length === 1 | ||
? "1 provisioner" | ||
: `${provisioners.length} provisioners`; | ||
|
||
const extraTags = Object.entries(provisioner.tags).filter( | ||
([key]) => key !== "scope" && key !== "owner", | ||
); | ||
|
||
return ( | ||
<div | ||
css={{ | ||
borderRadius: 8, | ||
border: `1px solid ${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", | ||
}} | ||
> | ||
{type === "builtin" && ( | ||
<div css={{ lineHeight: "160%" }}> | ||
<h4 css={{ fontWeight: 500, margin: 0 }}> | ||
Built-in provisioners | ||
</h4> | ||
<span css={{ color: theme.palette.text.secondary }}> | ||
{provisionerCount} — 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} —{" "} | ||
{allProvisionersAreSameVersion ? ( | ||
<code>{provisionerVersion}</code> | ||
) : ( | ||
<span>Multiple versions</span> | ||
)} | ||
</span> | ||
</div> | ||
)} | ||
{type === "key" && ( | ||
<div css={{ lineHeight: "160%" }}> | ||
<h4 css={{ fontWeight: 500, margin: 0 }}> | ||
Key group – {keyName} | ||
</h4> | ||
<span css={{ color: theme.palette.text.secondary }}> | ||
{provisionerCount} —{" "} | ||
{allProvisionersAreSameVersion ? ( | ||
<code>{provisionerVersion}</code> | ||
) : ( | ||
<span>Multiple versions</span> | ||
)} | ||
</span> | ||
</div> | ||
)} | ||
</div> | ||
<div | ||
css={{ | ||
marginLeft: "auto", | ||
display: "flex", | ||
flexWrap: "wrap", | ||
gap: 12, | ||
justifyContent: "right", | ||
}} | ||
> | ||
<Tooltip title="Scope"> | ||
<Pill size="lg" icon={iconScope}> | ||
<span | ||
css={{ | ||
":first-letter": { textTransform: "uppercase" }, | ||
aslilac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}} | ||
> | ||
{daemonScope} | ||
</span> | ||
</Pill> | ||
</Tooltip> | ||
{type === "key" && | ||
extraTags.map(([key, value]) => ( | ||
<ProvisionerTag key={key} tagName={key} tagValue={value} /> | ||
))} | ||
</div> | ||
</header> | ||
|
||
{showDetails && ( | ||
<div | ||
css={{ | ||
padding: "0 24px 24px", | ||
display: "flex", | ||
gap: 12, | ||
flexWrap: "wrap", | ||
}} | ||
> | ||
{provisioners.map((provisioner) => ( | ||
<div | ||
key={provisioner.id} | ||
css={{ | ||
borderRadius: 8, | ||
border: `1px solid ${theme.palette.divider}`, | ||
fontSize: 14, | ||
padding: "12px 18px", | ||
width: 310, | ||
}} | ||
> | ||
<div css={{ lineHeight: "160%" }}> | ||
<h4 css={{ fontWeight: 500, margin: 0 }}>{provisioner.name}</h4> | ||
<span css={{ color: theme.palette.text.secondary }}> | ||
{type === "builtin" ? ( | ||
<span>Built-in</span> | ||
) : ( | ||
<> | ||
{upToDate ? "Up to date" : provisioner.version} —{" "} | ||
{provisioner.last_seen_at && ( | ||
<span data-chromatic="ignore"> | ||
Last seen {createDayString(provisioner.last_seen_at)} | ||
</span> | ||
)} | ||
</> | ||
)} | ||
</span> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
)} | ||
|
||
<div | ||
css={{ | ||
borderTop: `1px solid ${theme.palette.divider}`, | ||
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "space-between", | ||
padding: "8px 8px 8px 24px", | ||
fontSize: 12, | ||
color: theme.palette.text.secondary, | ||
}} | ||
> | ||
<span>No warnings from {provisionerCount}</span> | ||
<Button | ||
variant="text" | ||
css={{ | ||
display: "flex", | ||
alignItems: "center", | ||
gap: 4, | ||
color: theme.roles.info.text, | ||
fontSize: "inherit", | ||
}} | ||
onClick={() => setShowDetails((it) => !it)} | ||
> | ||
{showDetails ? "Hide" : "Show"} provisioner details{" "} | ||
<DropdownArrow close={showDetails} /> | ||
</Button> | ||
</div> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,49 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { MockProvisioner, MockUserProvisioner } from "testHelpers/entities"; | ||
import { | ||
MockBuildInfo, | ||
MockProvisioner, | ||
MockProvisioner2, | ||
MockProvisionerWithTags, | ||
MockUserProvisioner, | ||
} from "testHelpers/entities"; | ||
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView"; | ||
|
||
const meta: Meta<typeof OrganizationProvisionersPageView> = { | ||
title: "pages/OrganizationProvisionersPage", | ||
component: OrganizationProvisionersPageView, | ||
args: { | ||
buildInfo: MockBuildInfo, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof OrganizationProvisionersPageView>; | ||
|
||
export const Provisioners: Story = { | ||
args: { | ||
provisioners: [ | ||
MockProvisioner, | ||
MockUserProvisioner, | ||
{ | ||
...MockProvisioner, | ||
tags: { | ||
...MockProvisioner.tags, | ||
都市: "ユタ", | ||
きっぷ: "yes", | ||
ちいさい: "no", | ||
}, | ||
}, | ||
], | ||
provisioners: { | ||
builtin: [MockProvisioner, MockProvisioner2], | ||
psk: [MockProvisioner, MockUserProvisioner, MockProvisionerWithTags], | ||
userAuth: [], | ||
keys: new Map([ | ||
[ | ||
"ケイラ", | ||
[ | ||
{ | ||
...MockProvisioner, | ||
tags: { | ||
...MockProvisioner.tags, | ||
都市: "ユタ", | ||
きっぷ: "yes", | ||
ちいさい: "no", | ||
}, | ||
warnings: [ | ||
{ code: "EUNKNOWN", message: "私は日本語が話せません" }, | ||
aslilac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], | ||
}, | ||
], | ||
], | ||
]), | ||
}, | ||
}, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.