Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3a45c12

Browse files
committed
feat(site): add Organization Provisioner Keys view
1 parentc7917ea commit3a45c12

File tree

8 files changed

+348
-2
lines changed

8 files changed

+348
-2
lines changed

‎site/src/api/queries/organizations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ const getProvisionerDaemonGroupsKey = (organization: string) => [
187187
"provisionerDaemons",
188188
];
189189

190-
constprovisionerDaemonGroups=(organization:string)=>{
190+
exportconstprovisionerDaemonGroups=(organization:string)=>{
191191
return{
192192
queryKey:getProvisionerDaemonGroupsKey(organization),
193193
queryFn:()=>API.getProvisionerDaemonGroupsByOrganization(organization),

‎site/src/modules/management/OrganizationSidebarView.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ const OrganizationSettingsNavigation: FC<
190190
>
191191
Provisioners
192192
</SettingsSidebarNavItem>
193+
<SettingsSidebarNavItem
194+
href={urlForSubpage(organization.name,"provisioner-keys")}
195+
>
196+
Provisioner Keys
197+
</SettingsSidebarNavItem>
193198
<SettingsSidebarNavItem
194199
href={urlForSubpage(organization.name,"provisioner-jobs")}
195200
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import{buildInfo}from"api/queries/buildInfo";
2+
import{provisionerDaemonGroups}from"api/queries/organizations";
3+
import{EmptyState}from"components/EmptyState/EmptyState";
4+
import{useEmbeddedMetadata}from"hooks/useEmbeddedMetadata";
5+
import{useDashboard}from"modules/dashboard/useDashboard";
6+
import{useOrganizationSettings}from"modules/management/OrganizationSettingsLayout";
7+
import{RequirePermission}from"modules/permissions/RequirePermission";
8+
importtype{FC}from"react";
9+
import{Helmet}from"react-helmet-async";
10+
import{useQuery}from"react-query";
11+
import{useParams}from"react-router-dom";
12+
import{pageTitle}from"utils/page";
13+
import{OrganizationProvisionerKeysPageView}from"./OrganizationProvisionerKeysPageView";
14+
15+
constOrganizationProvisionerKeysPage:FC=()=>{
16+
const{organization:organizationName}=useParams()as{
17+
organization:string;
18+
};
19+
const{ organization, organizationPermissions}=useOrganizationSettings();
20+
const{ entitlements}=useDashboard();
21+
const{ metadata}=useEmbeddedMetadata();
22+
constbuildInfoQuery=useQuery(buildInfo(metadata["build-info"]));
23+
constprovisionerKeyDaemonsQuery=useQuery({
24+
...provisionerDaemonGroups(organizationName),
25+
});
26+
27+
if(!organization){
28+
return<EmptyStatemessage="Organization not found"/>;
29+
}
30+
31+
consthelmet=(
32+
<Helmet>
33+
<title>
34+
{pageTitle(
35+
"Provisioner Keys",
36+
organization.display_name||organization.name,
37+
)}
38+
</title>
39+
</Helmet>
40+
);
41+
42+
if(!organizationPermissions?.viewProvisioners){
43+
return(
44+
<>
45+
{helmet}
46+
<RequirePermissionisFeatureVisible={false}/>
47+
</>
48+
);
49+
}
50+
51+
return(
52+
<>
53+
{helmet}
54+
<OrganizationProvisionerKeysPageView
55+
showPaywall={!entitlements.features.multiple_organizations.enabled}
56+
buildVersion={buildInfoQuery.data?.version}
57+
provisionerKeyDaemons={provisionerKeyDaemonsQuery.data}
58+
error={provisionerKeyDaemonsQuery.error}
59+
onRetry={provisionerKeyDaemonsQuery.refetch}
60+
/>
61+
</>
62+
);
63+
};
64+
65+
exportdefaultOrganizationProvisionerKeysPage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{MockProvisioner,MockProvisionerKey}from"testHelpers/entities";
3+
import{OrganizationProvisionerKeysPageView}from"./OrganizationProvisionerKeysPageView";
4+
5+
constmockProvisionerKeyDaemons=[
6+
{
7+
key:{
8+
...MockProvisionerKey,
9+
},
10+
daemons:[
11+
{
12+
...MockProvisioner,
13+
},
14+
],
15+
},
16+
];
17+
18+
constmeta:Meta<typeofOrganizationProvisionerKeysPageView>={
19+
title:"pages/OrganizationProvisionerKeysPage",
20+
component:OrganizationProvisionerKeysPageView,
21+
args:{
22+
error:undefined,
23+
provisionerKeyDaemons:mockProvisionerKeyDaemons,
24+
onRetry:()=>{},
25+
},
26+
};
27+
28+
exportdefaultmeta;
29+
typeStory=StoryObj<typeofOrganizationProvisionerKeysPageView>;
30+
31+
exportconstExample:Story={};
32+
33+
exportconstPaywalled:Story={
34+
args:{
35+
showPaywall:true,
36+
},
37+
};
38+
39+
exportconstNoProvisionerKeys:Story={
40+
args:{
41+
provisionerKeyDaemons:[],
42+
},
43+
};
44+
45+
exportconstErrorLoadingProvisionerKeys:Story={
46+
args:{
47+
error:"Failed to load provisioner keys",
48+
},
49+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import{
2+
typeProvisionerKeyDaemons,
3+
ProvisionerKeyIDPSK,
4+
ProvisionerKeyIDUserAuth,
5+
}from"api/typesGenerated";
6+
import{Button}from"components/Button/Button";
7+
import{EmptyState}from"components/EmptyState/EmptyState";
8+
import{Link}from"components/Link/Link";
9+
import{Loader}from"components/Loader/Loader";
10+
import{Paywall}from"components/Paywall/Paywall";
11+
import{
12+
SettingsHeader,
13+
SettingsHeaderDescription,
14+
SettingsHeaderTitle,
15+
}from"components/SettingsHeader/SettingsHeader";
16+
import{
17+
Table,
18+
TableBody,
19+
TableCell,
20+
TableHead,
21+
TableHeader,
22+
TableRow,
23+
}from"components/Table/Table";
24+
importtype{FC}from"react";
25+
import{docs}from"utils/docs";
26+
import{ProvisionerKeyRow}from"./ProvisionerKeyRow";
27+
28+
interfaceOrganizationProvisionerKeysPageViewProps{
29+
showPaywall:boolean|undefined;
30+
provisionerKeyDaemons:ProvisionerKeyDaemons[]|undefined;
31+
buildVersion:string|undefined;
32+
error:unknown;
33+
onRetry:()=>void;
34+
}
35+
36+
exportconstOrganizationProvisionerKeysPageView:FC<
37+
OrganizationProvisionerKeysPageViewProps
38+
>=({ showPaywall, provisionerKeyDaemons, buildVersion, error, onRetry})=>{
39+
return(
40+
<section>
41+
<SettingsHeader>
42+
<SettingsHeaderTitle>Provisioner Keys</SettingsHeaderTitle>
43+
<SettingsHeaderDescription>
44+
Manage provisioner keys used to authenticate provisioner instances.{" "}
45+
<Linkhref={docs("/admin/provisioners")}>View docs</Link>
46+
</SettingsHeaderDescription>
47+
</SettingsHeader>
48+
49+
{showPaywall ?(
50+
<Paywall
51+
message="Provisioners"
52+
description="Provisioners run your Terraform to create templates and workspaces. You need a Premium license to use this feature for multiple organizations."
53+
documentationLink={docs("/")}
54+
/>
55+
) :(
56+
<TableclassName="mt-6">
57+
<TableHeader>
58+
<TableRow>
59+
<TableHead>Created</TableHead>
60+
<TableHead>Name</TableHead>
61+
<TableHead>ID</TableHead>
62+
<TableHead>Tags</TableHead>
63+
<TableHead>#</TableHead>
64+
</TableRow>
65+
</TableHeader>
66+
<TableBody>
67+
{provisionerKeyDaemons ?(
68+
provisionerKeyDaemons.length===0 ?(
69+
<TableRow>
70+
<TableCellcolSpan={5}>
71+
<EmptyState
72+
message="No provisioner keys"
73+
description="Create your first provisioner key to authenticate external provisioner daemons."
74+
/>
75+
</TableCell>
76+
</TableRow>
77+
) :(
78+
provisionerKeyDaemons
79+
.filter((pkd)=>{
80+
console.debug(pkd);
81+
return(
82+
// pkd.key.id !== ProvisionerKeyIDBuiltIn &&
83+
pkd.key.id!==ProvisionerKeyIDUserAuth&&
84+
pkd.key.id!==ProvisionerKeyIDPSK
85+
);
86+
})
87+
.map((pkd)=>(
88+
<spankey={pkd.key.id}>
89+
<ProvisionerKeyRow
90+
provisionerKey={pkd.key}
91+
provisioners={pkd.daemons}
92+
defaultIsOpen={false}
93+
/>
94+
</span>
95+
))
96+
)
97+
) :error ?(
98+
<TableRow>
99+
<TableCellcolSpan={5}>
100+
<EmptyState
101+
message="Error loading provisioner keys"
102+
cta={
103+
<ButtononClick={onRetry}size="sm">
104+
Retry
105+
</Button>
106+
}
107+
/>
108+
</TableCell>
109+
</TableRow>
110+
) :(
111+
<TableRow>
112+
<TableCellcolSpan={999}>
113+
<Loader/>
114+
</TableCell>
115+
</TableRow>
116+
)}
117+
</TableBody>
118+
</Table>
119+
)}
120+
</section>
121+
);
122+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
importtype{ProvisionerDaemon,ProvisionerKey}from"api/typesGenerated";
2+
import{Button}from"components/Button/Button";
3+
import{CopyButton}from"components/CopyButton/CopyButton";
4+
import{TableCell,TableRow}from"components/Table/Table";
5+
import{ChevronDownIcon,ChevronRightIcon}from"lucide-react";
6+
import{ProvisionerTag}from"modules/provisioners/ProvisionerTags";
7+
import{typeFC,useState}from"react";
8+
import{LinkasRouterLink}from"react-router-dom";
9+
import{cn}from"utils/cn";
10+
import{relativeTime}from"utils/time";
11+
12+
typeProvisionerKeyRowProps={
13+
readonlyprovisionerKey:ProvisionerKey;
14+
readonlyprovisioners:readonlyProvisionerDaemon[];
15+
defaultIsOpen:boolean;
16+
};
17+
18+
exportconstProvisionerKeyRow:FC<ProvisionerKeyRowProps>=({
19+
provisionerKey,
20+
provisioners,
21+
defaultIsOpen=false,
22+
})=>{
23+
const[isOpen,setIsOpen]=useState(defaultIsOpen);
24+
25+
return(
26+
<>
27+
<TableRowkey={provisionerKey.id}>
28+
<TableCell>
29+
<Button
30+
variant="subtle"
31+
size="sm"
32+
className={cn([
33+
isOpen&&"text-content-primary",
34+
"p-0 h-auto min-w-0 align-middle",
35+
])}
36+
onClick={()=>setIsOpen((v)=>!v)}
37+
>
38+
{isOpen ?<ChevronDownIcon/> :<ChevronRightIcon/>}
39+
<spanclassName="sr-only">({isOpen ?"Hide" :"Show more"})</span>
40+
<spanclassName="block first-letter:uppercase">
41+
{relativeTime(newDate(provisionerKey.created_at))}
42+
</span>
43+
</Button>
44+
</TableCell>
45+
<TableCell>{provisionerKey.name}</TableCell>
46+
<TableCell>
47+
<spanclassName="font-mono text-content-primary">
48+
{provisionerKey.id}
49+
</span>
50+
<CopyButtontext={provisionerKey.id}label="Copy ID"/>
51+
</TableCell>
52+
<TableCell>
53+
{Object.entries(provisionerKey.tags).map(([k,v])=>(
54+
<spankey={k}>
55+
<ProvisionerTaglabel={k}value={v}/>
56+
</span>
57+
))}
58+
</TableCell>
59+
<TableCellclassName="text-right">{provisioners.length}</TableCell>
60+
</TableRow>
61+
62+
{isOpen&&(
63+
<TableRow>
64+
<TableCellcolSpan={999}className="p-4 border-t-0">
65+
<dl>
66+
<dt>Provisioners:</dt>
67+
{provisioners.length===0 ?(
68+
<ddclassName="text-muted-foreground">
69+
No provisioners found for this key
70+
</dd>
71+
) :(
72+
provisioners.map((provisioner)=>(
73+
<ddkey={provisioner.id}>
74+
{provisioner.name} ({provisioner.id}){" "}
75+
<CopyButton
76+
text={provisioner.id}
77+
label="Copy provisioner ID"
78+
/>
79+
<Buttonsize="xs"variant="outline"asChild>
80+
<RouterLink
81+
to={`../provisioners?${newURLSearchParams({ids:provisioner.id})}`}
82+
>
83+
View provisioner
84+
</RouterLink>
85+
</Button>
86+
</dd>
87+
))
88+
)}
89+
</dl>
90+
</TableCell>
91+
</TableRow>
92+
)}
93+
</>
94+
);
95+
};

‎site/src/router.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,12 @@ const ChangePasswordPage = lazy(
313313
constIdpOrgSyncPage=lazy(
314314
()=>import("./pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage"),
315315
);
316+
constProvisionerKeysPage=lazy(
317+
()=>
318+
import(
319+
"./pages/OrganizationSettingsPage/OrganizationProvisionerKeysPage/OrganizationProvisionerKeysPage"
320+
),
321+
);
316322
constProvisionerJobsPage=lazy(
317323
()=>
318324
import(
@@ -449,6 +455,10 @@ export const router = createBrowserRouter(
449455
path="provisioner-jobs"
450456
element={<ProvisionerJobsPage/>}
451457
/>
458+
<Route
459+
path="provisioner-keys"
460+
element={<ProvisionerKeysPage/>}
461+
/>
452462
<Routepath="idp-sync"element={<OrganizationIdPSyncPage/>}/>
453463
<Routepath="settings"element={<OrganizationSettingsPage/>}/>
454464
</Route>

‎site/src/testHelpers/entities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ export const MockOrganizationMember2: TypesGen.OrganizationMemberWithUserData =
561561
roles:[],
562562
};
563563

564-
constMockProvisionerKey:TypesGen.ProvisionerKey={
564+
exportconstMockProvisionerKey:TypesGen.ProvisionerKey={
565565
id:"test-provisioner-key",
566566
organization:MockOrganization.id,
567567
created_at:"2022-05-17T17:39:01.382927298Z",

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp