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

Commitf30fa76

Browse files
committed
feat(site): add Organization Provisioner Keys view
1 parent3dbd424 commitf30fa76

File tree

8 files changed

+349
-2
lines changed

8 files changed

+349
-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
>
Lines changed: 65 additions & 0 deletions
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;
Lines changed: 49 additions & 0 deletions
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+
};
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import{
2+
typeProvisionerKeyDaemons,
3+
ProvisionerKeyIDBuiltIn,
4+
ProvisionerKeyIDPSK,
5+
ProvisionerKeyIDUserAuth,
6+
}from"api/typesGenerated";
7+
import{Button}from"components/Button/Button";
8+
import{EmptyState}from"components/EmptyState/EmptyState";
9+
import{Link}from"components/Link/Link";
10+
import{Loader}from"components/Loader/Loader";
11+
import{Paywall}from"components/Paywall/Paywall";
12+
import{
13+
SettingsHeader,
14+
SettingsHeaderDescription,
15+
SettingsHeaderTitle,
16+
}from"components/SettingsHeader/SettingsHeader";
17+
import{
18+
Table,
19+
TableBody,
20+
TableCell,
21+
TableHead,
22+
TableHeader,
23+
TableRow,
24+
}from"components/Table/Table";
25+
importtype{FC}from"react";
26+
import{docs}from"utils/docs";
27+
import{ProvisionerKeyRow}from"./ProvisionerKeyRow";
28+
29+
interfaceOrganizationProvisionerKeysPageViewProps{
30+
showPaywall:boolean|undefined;
31+
provisionerKeyDaemons:ProvisionerKeyDaemons[]|undefined;
32+
buildVersion:string|undefined;
33+
error:unknown;
34+
onRetry:()=>void;
35+
}
36+
37+
exportconstOrganizationProvisionerKeysPageView:FC<
38+
OrganizationProvisionerKeysPageViewProps
39+
>=({ showPaywall, provisionerKeyDaemons, buildVersion, error, onRetry})=>{
40+
return(
41+
<section>
42+
<SettingsHeader>
43+
<SettingsHeaderTitle>Provisioner Keys</SettingsHeaderTitle>
44+
<SettingsHeaderDescription>
45+
Manage provisioner keys used to authenticate provisioner instances.{" "}
46+
<Linkhref={docs("/admin/provisioners")}>View docs</Link>
47+
</SettingsHeaderDescription>
48+
</SettingsHeader>
49+
50+
{showPaywall ?(
51+
<Paywall
52+
message="Provisioners"
53+
description="Provisioners run your Terraform to create templates and workspaces. You need a Premium license to use this feature for multiple organizations."
54+
documentationLink={docs("/")}
55+
/>
56+
) :(
57+
<TableclassName="mt-6">
58+
<TableHeader>
59+
<TableRow>
60+
<TableHead>Created</TableHead>
61+
<TableHead>Name</TableHead>
62+
<TableHead>ID</TableHead>
63+
<TableHead>Tags</TableHead>
64+
<TableHead>Provisioners</TableHead>
65+
</TableRow>
66+
</TableHeader>
67+
<TableBody>
68+
{provisionerKeyDaemons ?(
69+
provisionerKeyDaemons.length===0 ?(
70+
<TableRow>
71+
<TableCellcolSpan={5}>
72+
<EmptyState
73+
message="No provisioner keys"
74+
description="Create your first provisioner key to authenticate external provisioner daemons."
75+
/>
76+
</TableCell>
77+
</TableRow>
78+
) :(
79+
provisionerKeyDaemons
80+
.filter((pkd)=>{
81+
return(
82+
pkd.key.id!==ProvisionerKeyIDBuiltIn&&
83+
pkd.key.id!==ProvisionerKeyIDUserAuth&&
84+
pkd.key.id!==ProvisionerKeyIDPSK
85+
);
86+
})
87+
.map((pkd)=>(
88+
<ProvisionerKeyRow
89+
key={pkd.key.id}
90+
provisionerKey={pkd.key}
91+
provisioners={pkd.daemons}
92+
defaultIsOpen={false}
93+
/>
94+
))
95+
)
96+
) :error ?(
97+
<TableRow>
98+
<TableCellcolSpan={5}>
99+
<EmptyState
100+
message="Error loading provisioner keys"
101+
cta={
102+
<ButtononClick={onRetry}size="sm">
103+
Retry
104+
</Button>
105+
}
106+
/>
107+
</TableCell>
108+
</TableRow>
109+
) :(
110+
<TableRow>
111+
<TableCellcolSpan={999}>
112+
<Loader/>
113+
</TableCell>
114+
</TableRow>
115+
)}
116+
</TableBody>
117+
</Table>
118+
)}
119+
</section>
120+
);
121+
};
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
<TableCell>{provisioners.length}</TableCell>
60+
</TableRow>
61+
62+
{isOpen&&(
63+
<TableRow>
64+
<TableCellcolSpan={999}className="p-4 border-t-0">
65+
{provisioners.length===0 ?(
66+
<spanclassName="text-muted-foreground">
67+
No provisioners found for this key.
68+
</span>
69+
) :(
70+
<dl>
71+
<dt>Provisioners:</dt>
72+
{provisioners.map((provisioner)=>(
73+
<ddkey={provisioner.id}>
74+
<spanclassName="font-mono text-content-primary">
75+
{provisioner.name} ({provisioner.id}){" "}
76+
</span>
77+
<CopyButton
78+
text={provisioner.id}
79+
label="Copy provisioner ID"
80+
/>
81+
<Buttonsize="xs"variant="outline"asChild>
82+
<RouterLink
83+
to={`../provisioners?${newURLSearchParams({ids:provisioner.id})}`}
84+
>
85+
View provisioner
86+
</RouterLink>
87+
</Button>
88+
</dd>
89+
))}
90+
</dl>
91+
)}
92+
</TableCell>
93+
</TableRow>
94+
)}
95+
</>
96+
);
97+
};

‎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