From f30fa7605345ab2d36003aea180f249175fa4238 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 16 May 2025 16:55:01 +0100 Subject: [PATCH 1/9] feat(site): add Organization Provisioner Keys view --- site/src/api/queries/organizations.ts | 2 +- .../management/OrganizationSidebarView.tsx | 5 + .../OrganizationProvisionerKeysPage.tsx | 65 ++++++++++ ...izationProvisionerKeysPageView.stories.tsx | 49 +++++++ .../OrganizationProvisionerKeysPageView.tsx | 121 ++++++++++++++++++ .../ProvisionerKeyRow.tsx | 97 ++++++++++++++ site/src/router.tsx | 10 ++ site/src/testHelpers/entities.ts | 2 +- 8 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index c7b42f5f0e79f..608b2fa2a1ac4 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -187,7 +187,7 @@ const getProvisionerDaemonGroupsKey = (organization: string) => [ "provisionerDaemons", ]; -const provisionerDaemonGroups = (organization: string) => { +export const provisionerDaemonGroups = (organization: string) => { return { queryKey: getProvisionerDaemonGroupsKey(organization), queryFn: () => API.getProvisionerDaemonGroupsByOrganization(organization), diff --git a/site/src/modules/management/OrganizationSidebarView.tsx b/site/src/modules/management/OrganizationSidebarView.tsx index a03dc62b65c0e..745268278da49 100644 --- a/site/src/modules/management/OrganizationSidebarView.tsx +++ b/site/src/modules/management/OrganizationSidebarView.tsx @@ -190,6 +190,11 @@ const OrganizationSettingsNavigation: FC< > Provisioners + + Provisioner Keys + diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx new file mode 100644 index 0000000000000..901b608aa93b5 --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx @@ -0,0 +1,65 @@ +import { buildInfo } from "api/queries/buildInfo"; +import { provisionerDaemonGroups } from "api/queries/organizations"; +import { EmptyState } from "components/EmptyState/EmptyState"; +import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; +import { useDashboard } from "modules/dashboard/useDashboard"; +import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; +import type { FC } from "react"; +import { Helmet } from "react-helmet-async"; +import { useQuery } from "react-query"; +import { useParams } from "react-router-dom"; +import { pageTitle } from "utils/page"; +import { OrganizationProvisionerKeysPageView } from "./OrganizationProvisionerKeysPageView"; + +const OrganizationProvisionerKeysPage: FC = () => { + const { organization: organizationName } = useParams() as { + organization: string; + }; + const { organization, organizationPermissions } = useOrganizationSettings(); + const { entitlements } = useDashboard(); + const { metadata } = useEmbeddedMetadata(); + const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); + const provisionerKeyDaemonsQuery = useQuery({ + ...provisionerDaemonGroups(organizationName), + }); + + if (!organization) { + return ; + } + + const helmet = ( + + + {pageTitle( + "Provisioner Keys", + organization.display_name || organization.name, + )} + + + ); + + if (!organizationPermissions?.viewProvisioners) { + return ( + <> + {helmet} + + + ); + } + + return ( + <> + {helmet} + + + ); +}; + +export default OrganizationProvisionerKeysPage; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx new file mode 100644 index 0000000000000..e03f28fcf8a11 --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx @@ -0,0 +1,49 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { MockProvisioner, MockProvisionerKey } from "testHelpers/entities"; +import { OrganizationProvisionerKeysPageView } from "./OrganizationProvisionerKeysPageView"; + +const mockProvisionerKeyDaemons = [ + { + key: { + ...MockProvisionerKey, + }, + daemons: [ + { + ...MockProvisioner, + }, + ], + }, +]; + +const meta: Meta = { + title: "pages/OrganizationProvisionerKeysPage", + component: OrganizationProvisionerKeysPageView, + args: { + error: undefined, + provisionerKeyDaemons: mockProvisionerKeyDaemons, + onRetry: () => {}, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; + +export const Paywalled: Story = { + args: { + showPaywall: true, + }, +}; + +export const NoProvisionerKeys: Story = { + args: { + provisionerKeyDaemons: [], + }, +}; + +export const ErrorLoadingProvisionerKeys: Story = { + args: { + error: "Failed to load provisioner keys", + }, +}; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx new file mode 100644 index 0000000000000..12a6a10d9e46e --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx @@ -0,0 +1,121 @@ +import { + type ProvisionerKeyDaemons, + ProvisionerKeyIDBuiltIn, + ProvisionerKeyIDPSK, + ProvisionerKeyIDUserAuth, +} from "api/typesGenerated"; +import { Button } from "components/Button/Button"; +import { EmptyState } from "components/EmptyState/EmptyState"; +import { Link } from "components/Link/Link"; +import { Loader } from "components/Loader/Loader"; +import { Paywall } from "components/Paywall/Paywall"; +import { + SettingsHeader, + SettingsHeaderDescription, + SettingsHeaderTitle, +} from "components/SettingsHeader/SettingsHeader"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "components/Table/Table"; +import type { FC } from "react"; +import { docs } from "utils/docs"; +import { ProvisionerKeyRow } from "./ProvisionerKeyRow"; + +interface OrganizationProvisionerKeysPageViewProps { + showPaywall: boolean | undefined; + provisionerKeyDaemons: ProvisionerKeyDaemons[] | undefined; + buildVersion: string | undefined; + error: unknown; + onRetry: () => void; +} + +export const OrganizationProvisionerKeysPageView: FC< + OrganizationProvisionerKeysPageViewProps +> = ({ showPaywall, provisionerKeyDaemons, buildVersion, error, onRetry }) => { + return ( +
+ + Provisioner Keys + + Manage provisioner keys used to authenticate provisioner instances.{" "} + View docs + + + + {showPaywall ? ( + + ) : ( + + + + Created + Name + ID + Tags + Provisioners + + + + {provisionerKeyDaemons ? ( + provisionerKeyDaemons.length === 0 ? ( + + + + + + ) : ( + provisionerKeyDaemons + .filter((pkd) => { + return ( + pkd.key.id !== ProvisionerKeyIDBuiltIn && + pkd.key.id !== ProvisionerKeyIDUserAuth && + pkd.key.id !== ProvisionerKeyIDPSK + ); + }) + .map((pkd) => ( + + )) + ) + ) : error ? ( + + + + Retry + + } + /> + + + ) : ( + + + + + + )} + +
+ )} +
+ ); +}; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx new file mode 100644 index 0000000000000..f9273c1ad0c71 --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx @@ -0,0 +1,97 @@ +import type { ProvisionerDaemon, ProvisionerKey } from "api/typesGenerated"; +import { Button } from "components/Button/Button"; +import { CopyButton } from "components/CopyButton/CopyButton"; +import { TableCell, TableRow } from "components/Table/Table"; +import { ChevronDownIcon, ChevronRightIcon } from "lucide-react"; +import { ProvisionerTag } from "modules/provisioners/ProvisionerTags"; +import { type FC, useState } from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { cn } from "utils/cn"; +import { relativeTime } from "utils/time"; + +type ProvisionerKeyRowProps = { + readonly provisionerKey: ProvisionerKey; + readonly provisioners: readonly ProvisionerDaemon[]; + defaultIsOpen: boolean; +}; + +export const ProvisionerKeyRow: FC = ({ + provisionerKey, + provisioners, + defaultIsOpen = false, +}) => { + const [isOpen, setIsOpen] = useState(defaultIsOpen); + + return ( + <> + + + + + {provisionerKey.name} + + + {provisionerKey.id} + + + + + {Object.entries(provisionerKey.tags).map(([k, v]) => ( + + + + ))} + + {provisioners.length} + + + {isOpen && ( + + + {provisioners.length === 0 ? ( + + No provisioners found for this key. + + ) : ( +
+
Provisioners:
+ {provisioners.map((provisioner) => ( +
+ + {provisioner.name} ({provisioner.id}){" "} + + + +
+ ))} +
+ )} +
+
+ )} + + ); +}; diff --git a/site/src/router.tsx b/site/src/router.tsx index 534d4037d02b3..5784696a16f2d 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -313,6 +313,12 @@ const ChangePasswordPage = lazy( const IdpOrgSyncPage = lazy( () => import("./pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage"), ); +const ProvisionerKeysPage = lazy( + () => + import( + "./pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage" + ), +); const ProvisionerJobsPage = lazy( () => import( @@ -449,6 +455,10 @@ export const router = createBrowserRouter( path="provisioner-jobs" element={} /> + } + /> } /> } /> diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 6351e74d3c54d..e09b196a82446 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -561,7 +561,7 @@ export const MockOrganizationMember2: TypesGen.OrganizationMemberWithUserData = roles: [], }; -const MockProvisionerKey: TypesGen.ProvisionerKey = { +export const MockProvisionerKey: TypesGen.ProvisionerKey = { id: "test-provisioner-key", organization: MockOrganization.id, created_at: "2022-05-17T17:39:01.382927298Z", From 6f9a9e95b1de422e1fa10e79570932dc8825888f Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 16 May 2025 19:07:25 +0100 Subject: [PATCH 2/9] render provisioners in a table --- .../OrganizationProvisionerKeysPageView.tsx | 1 + .../ProvisionerKeyRow.tsx | 66 +++++++++++-------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx index 12a6a10d9e46e..4b1bd7e09a1c2 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx @@ -87,6 +87,7 @@ export const OrganizationProvisionerKeysPageView: FC< .map((pkd) => ( = ({ provisionerKey, provisioners, + buildVersion, defaultIsOpen = false, }) => { const [isOpen, setIsOpen] = useState(defaultIsOpen); @@ -61,33 +70,38 @@ export const ProvisionerKeyRow: FC = ({ {isOpen && ( - + {provisioners.length === 0 ? ( - - No provisioners found for this key. - + + + + No provisioners found for this key. + + + ) : ( -
-
Provisioners:
- {provisioners.map((provisioner) => ( -
- - {provisioner.name} ({provisioner.id}){" "} - - - -
+ + + + Name + Key + Version + Status + Tags + + + + + + {provisioners.map((p) => ( + ))} - +
)} From 2118a078bb6f83165eb879f67bd40d6847bbc430 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 19 May 2025 11:57:03 +0100 Subject: [PATCH 3/9] update storybook --- ...izationProvisionerKeysPageView.stories.tsx | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx index e03f28fcf8a11..074036a951c6d 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx @@ -1,5 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { MockProvisioner, MockProvisionerKey } from "testHelpers/entities"; +import { + ProvisionerKeyIDBuiltIn, + ProvisionerKeyIDPSK, + ProvisionerKeyIDUserAuth, +} from "api/typesGenerated"; +import { + MockBuildInfo, + MockProvisioner, + MockProvisionerKey, +} from "testHelpers/entities"; import { OrganizationProvisionerKeysPageView } from "./OrganizationProvisionerKeysPageView"; const mockProvisionerKeyDaemons = [ @@ -10,9 +19,47 @@ const mockProvisionerKeyDaemons = [ daemons: [ { ...MockProvisioner, + name: "Test Provisioner 1", + id: "daemon-1", + }, + { + ...MockProvisioner, + name: "Test Provisioner 2", + id: "daemon-2", }, ], }, + { + key: { + ...MockProvisionerKey, + name: "no-daemons", + }, + daemons: [], + }, + // Built-in provisioners, user-auth, and PSK keys are not shown here. + { + key: { + ...MockProvisionerKey, + id: ProvisionerKeyIDBuiltIn, + name: "built-in", + }, + daemons: [], + }, + { + key: { + ...MockProvisionerKey, + id: ProvisionerKeyIDUserAuth, + name: "user-auth", + }, + daemons: [], + }, + { + key: { + ...MockProvisionerKey, + id: ProvisionerKeyIDPSK, + name: "PSK", + }, + }, ]; const meta: Meta = { @@ -28,22 +75,34 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Example: Story = {}; +export const Default: Story = { + args: { + buildVersion: MockBuildInfo.version, + error: undefined, + provisionerKeyDaemons: mockProvisionerKeyDaemons, + onRetry: () => {}, + showPaywall: false, + }, +}; export const Paywalled: Story = { + ...Default, args: { showPaywall: true, }, }; -export const NoProvisionerKeys: Story = { +export const Empty: Story = { + ...Default, args: { provisionerKeyDaemons: [], }, }; export const ErrorLoadingProvisionerKeys: Story = { + ...Default, args: { + provisionerKeyDaemons: [], error: "Failed to load provisioner keys", }, }; From 964b6f62ef33da0349f3fbe288c0311d369e7ea8 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 19 May 2025 12:03:03 +0100 Subject: [PATCH 4/9] fixup! update storybook --- .../OrganizationProvisionerKeysPageView.stories.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx index 074036a951c6d..6101e5a2b7433 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { + type ProvisionerKeyDaemons, ProvisionerKeyIDBuiltIn, ProvisionerKeyIDPSK, ProvisionerKeyIDUserAuth, @@ -11,7 +12,7 @@ import { } from "testHelpers/entities"; import { OrganizationProvisionerKeysPageView } from "./OrganizationProvisionerKeysPageView"; -const mockProvisionerKeyDaemons = [ +const mockProvisionerKeyDaemons: ProvisionerKeyDaemons[] = [ { key: { ...MockProvisionerKey, @@ -59,6 +60,7 @@ const mockProvisionerKeyDaemons = [ id: ProvisionerKeyIDPSK, name: "PSK", }, + daemons: [], }, ]; From f6c77e86cf1aed798c1a6db3ec43f651f5e8f3a1 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 19 May 2025 12:08:16 +0100 Subject: [PATCH 5/9] indent expanded row content --- .../OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx index e7e93d88b3900..15919998c2cb6 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx @@ -70,7 +70,11 @@ export const ProvisionerKeyRow: FC = ({ {isOpen && ( - + {provisioners.length === 0 ? ( From 2866a3b4e39cdbba9556f22596e4054fc4da4e17 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 19 May 2025 14:58:27 +0000 Subject: [PATCH 6/9] Few design changes --- site/src/components/Badge/Badge.tsx | 23 ++- .../modules/provisioners/ProvisionerTags.tsx | 2 +- .../OrganizationProvisionerKeysPage.tsx | 5 +- ...izationProvisionerKeysPageView.stories.tsx | 1 - .../OrganizationProvisionerKeysPageView.tsx | 25 +-- .../ProvisionerKeyRow.tsx | 149 ++++++++++-------- 6 files changed, 121 insertions(+), 84 deletions(-) diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index e6b23b8a4dd94..b4d405055bb98 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -9,7 +9,6 @@ import { cn } from "utils/cn"; const badgeVariants = cva( `inline-flex items-center rounded-md border px-2 py-1 transition-colors - focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 [&_svg]:pointer-events-none [&_svg]:pr-0.5 [&_svg]:py-0.5 [&_svg]:mr-0.5`, { variants: { @@ -30,11 +29,23 @@ const badgeVariants = cva( none: "border-transparent", solid: "border border-solid", }, + hover: { + false: null, + true: "no-underline focus:outline-none focus-visible:ring-2 focus-visible:ring-content-link", + }, }, + compoundVariants: [ + { + hover: true, + variant: "default", + class: "hover:bg-surface-tertiary", + }, + ], defaultVariants: { variant: "default", size: "md", border: "solid", + hover: false, }, }, ); @@ -46,14 +57,20 @@ export interface BadgeProps } export const Badge = forwardRef( - ({ className, variant, size, border, asChild = false, ...props }, ref) => { + ( + { className, variant, size, border, hover, asChild = false, ...props }, + ref, + ) => { const Comp = asChild ? Slot : "div"; return ( ); }, diff --git a/site/src/modules/provisioners/ProvisionerTags.tsx b/site/src/modules/provisioners/ProvisionerTags.tsx index b31be42df234f..667d2cb56ef15 100644 --- a/site/src/modules/provisioners/ProvisionerTags.tsx +++ b/site/src/modules/provisioners/ProvisionerTags.tsx @@ -9,7 +9,7 @@ export const ProvisionerTags: FC> = ({ return (
); }; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx index 901b608aa93b5..9e9b36be659c9 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx @@ -18,10 +18,10 @@ const OrganizationProvisionerKeysPage: FC = () => { }; const { organization, organizationPermissions } = useOrganizationSettings(); const { entitlements } = useDashboard(); - const { metadata } = useEmbeddedMetadata(); - const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); const provisionerKeyDaemonsQuery = useQuery({ ...provisionerDaemonGroups(organizationName), + select: (data) => + [...data].sort((a, b) => b.daemons.length - a.daemons.length), }); if (!organization) { @@ -53,7 +53,6 @@ const OrganizationProvisionerKeysPage: FC = () => { {helmet} ; export const Default: Story = { args: { - buildVersion: MockBuildInfo.version, error: undefined, provisionerKeyDaemons: mockProvisionerKeyDaemons, onRetry: () => {}, diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx index 4b1bd7e09a1c2..5373636308f15 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.tsx @@ -26,17 +26,24 @@ import type { FC } from "react"; import { docs } from "utils/docs"; import { ProvisionerKeyRow } from "./ProvisionerKeyRow"; +// If the user using provisioner keys for external provisioners you're unlikely to +// want to keep the built-in provisioners. +const HIDDEN_PROVISIONER_KEYS = [ + ProvisionerKeyIDBuiltIn, + ProvisionerKeyIDUserAuth, + ProvisionerKeyIDPSK, +]; + interface OrganizationProvisionerKeysPageViewProps { showPaywall: boolean | undefined; provisionerKeyDaemons: ProvisionerKeyDaemons[] | undefined; - buildVersion: string | undefined; error: unknown; onRetry: () => void; } export const OrganizationProvisionerKeysPageView: FC< OrganizationProvisionerKeysPageViewProps -> = ({ showPaywall, provisionerKeyDaemons, buildVersion, error, onRetry }) => { +> = ({ showPaywall, provisionerKeyDaemons, error, onRetry }) => { return (
@@ -57,11 +64,10 @@ export const OrganizationProvisionerKeysPageView: FC< - Created Name - ID Tags Provisioners + Created @@ -77,17 +83,12 @@ export const OrganizationProvisionerKeysPageView: FC< ) : ( provisionerKeyDaemons - .filter((pkd) => { - return ( - pkd.key.id !== ProvisionerKeyIDBuiltIn && - pkd.key.id !== ProvisionerKeyIDUserAuth && - pkd.key.id !== ProvisionerKeyIDPSK - ); - }) + .filter( + (pkd) => !HIDDEN_PROVISIONER_KEYS.includes(pkd.key.id), + ) .map((pkd) => ( = ({ provisionerKey, provisioners, - buildVersion, defaultIsOpen = false, }) => { const [isOpen, setIsOpen] = useState(defaultIsOpen); @@ -46,70 +41,96 @@ export const ProvisionerKeyRow: FC = ({ > {isOpen ? : } ({isOpen ? "Hide" : "Show more"}) - - {relativeTime(new Date(provisionerKey.created_at))} - + {provisionerKey.name} - {provisionerKey.name} - - {provisionerKey.id} - - + {Object.entries(provisionerKey.tags).length > 0 ? ( + + ) : ( + No tags + )} - {Object.entries(provisionerKey.tags).map(([k, v]) => ( - - - - ))} + {provisioners.length > 0 ? ( + + ) : ( + No provisioners + )} + + + + {relativeTime(new Date(provisionerKey.created_at))} + - {provisioners.length} {isOpen && ( - - {provisioners.length === 0 ? ( - - - - No provisioners found for this key. - - - - ) : ( -
- - - Name - Key - Version - Status - Tags - - - - - - {provisioners.map((p) => ( - - ))} -
- )} + +
+
Creation time:
+
{provisionerKey.created_at}
+ +
Tags:
+
+ + {Object.entries(provisionerKey.tags).length === 0 && ( + No tags + )} + {Object.entries(provisionerKey.tags).map(([key, value]) => ( + + ))} + +
+ +
Provisioners:
+
+ + {provisioners.length === 0 && ( + + No provisioners + + )} + {provisioners.map((provisioner) => ( + + + {provisionerKey.name} + + + ))} + +
+
)} ); }; + +type TruncateProvisionersProps = { + provisioners: readonly ProvisionerDaemon[]; +}; + +export const TruncateProvisioners: FC = ({ + provisioners, +}) => { + const firstProvisioner = provisioners[0]; + const remainderCount = provisioners.length - 1; + + return ( + + {firstProvisioner.name} + {remainderCount > 0 && +{remainderCount}} + + ); +}; From 5efc4bcd93964a9261d263521c8d6218a118783d Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 19 May 2025 15:02:51 +0000 Subject: [PATCH 7/9] FMT --- .../OrganizationProvisionerKeysPage.tsx | 2 -- .../OrganizationProvisionerKeysPageView.stories.tsx | 6 +----- .../OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx index 9e9b36be659c9..77bcfe10cb229 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage.tsx @@ -1,7 +1,5 @@ -import { buildInfo } from "api/queries/buildInfo"; import { provisionerDaemonGroups } from "api/queries/organizations"; import { EmptyState } from "components/EmptyState/EmptyState"; -import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useDashboard } from "modules/dashboard/useDashboard"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; import { RequirePermission } from "modules/permissions/RequirePermission"; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx index 95a9142b1fdb4..8bc5c35c069bd 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx @@ -5,11 +5,7 @@ import { ProvisionerKeyIDPSK, ProvisionerKeyIDUserAuth, } from "api/typesGenerated"; -import { - MockBuildInfo, - MockProvisioner, - MockProvisionerKey, -} from "testHelpers/entities"; +import { MockProvisioner, MockProvisionerKey } from "testHelpers/entities"; import { OrganizationProvisionerKeysPageView } from "./OrganizationProvisionerKeysPageView"; const mockProvisionerKeyDaemons: ProvisionerKeyDaemons[] = [ diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx index 5439f3f0f73b6..0c63daefc4c85 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx @@ -9,9 +9,9 @@ import { ProvisionerTruncateTags, } from "modules/provisioners/ProvisionerTags"; import { type FC, useState } from "react"; +import { Link as RouterLink } from "react-router-dom"; import { cn } from "utils/cn"; import { relativeTime } from "utils/time"; -import { Link as RouterLink } from "react-router-dom"; type ProvisionerKeyRowProps = { readonly provisionerKey: ProvisionerKey; From dffbd85549d708f54b8bccb8b8d264ff563e18cb Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 19 May 2025 15:04:21 +0000 Subject: [PATCH 8/9] Fix unexported --- .../OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx index 0c63daefc4c85..e1b337c85dacb 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/ProvisionerKeyRow.tsx @@ -121,7 +121,7 @@ type TruncateProvisionersProps = { provisioners: readonly ProvisionerDaemon[]; }; -export const TruncateProvisioners: FC = ({ +const TruncateProvisioners: FC = ({ provisioners, }) => { const firstProvisioner = provisioners[0]; From 3979e76c852c4130d8e44fe8d96c295934005290 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 19 May 2025 16:32:48 +0100 Subject: [PATCH 9/9] fix WithError story --- ...rganizationProvisionerKeysPageView.stories.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx index 8bc5c35c069bd..f30ea66175e07 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPageView.stories.tsx @@ -5,7 +5,11 @@ import { ProvisionerKeyIDPSK, ProvisionerKeyIDUserAuth, } from "api/typesGenerated"; -import { MockProvisioner, MockProvisionerKey } from "testHelpers/entities"; +import { + MockProvisioner, + MockProvisionerKey, + mockApiError, +} from "testHelpers/entities"; import { OrganizationProvisionerKeysPageView } from "./OrganizationProvisionerKeysPageView"; const mockProvisionerKeyDaemons: ProvisionerKeyDaemons[] = [ @@ -96,10 +100,13 @@ export const Empty: Story = { }, }; -export const ErrorLoadingProvisionerKeys: Story = { +export const WithError: Story = { ...Default, args: { - provisionerKeyDaemons: [], - error: "Failed to load provisioner keys", + provisionerKeyDaemons: undefined, + error: mockApiError({ + message: "Error loading provisioner keys", + detail: "Something went wrong. This is an unhelpful error message.", + }), }, };