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

Commitbd34d20

Browse files
committed
Merge branch 'resource_acl_list' of github.com:coder/coder into resource_acl_list
2 parents6e72286 +1324991 commitbd34d20

18 files changed

+1011
-311
lines changed

‎site/src/AppRouter.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import{useSelector}from"@xstate/react"
22
import{FeatureNames}from"api/types"
33
import{RequirePermission}from"components/RequirePermission/RequirePermission"
4+
import{TemplateLayout}from"components/TemplateLayout/TemplateLayout"
45
import{SetupPage}from"pages/SetupPage/SetupPage"
6+
importTemplateCollaboratorsPagefrom"pages/TemplatePage/TemplateCollaboratorsPage/TemplateCollaboratorsPage"
7+
importTemplateSummaryPagefrom"pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPage"
58
import{TemplateSettingsPage}from"pages/TemplateSettingsPage/TemplateSettingsPage"
69
import{FC,lazy,Suspense,useContext}from"react"
710
import{Route,Routes}from"react-router-dom"
@@ -33,7 +36,6 @@ const TerminalPage = lazy(() => import("./pages/TerminalPage/TerminalPage"))
3336
constWorkspacesPage=lazy(()=>import("./pages/WorkspacesPage/WorkspacesPage"))
3437
constCreateWorkspacePage=lazy(()=>import("./pages/CreateWorkspacePage/CreateWorkspacePage"))
3538
constAuditPage=lazy(()=>import("./pages/AuditPage/AuditPage"))
36-
constTemplatePage=lazy(()=>import("./pages/TemplatePage/TemplatePage"))
3739

3840
exportconstAppRouter:FC=()=>{
3941
constxServices=useContext(XServiceContext)
@@ -87,12 +89,22 @@ export const AppRouter: FC = () => {
8789

8890
<Routepath=":template">
8991
<Route
90-
index
9192
element={
9293
<AuthAndFrame>
93-
<TemplatePage/>
94+
<TemplateLayout/>
9495
</AuthAndFrame>
9596
}
97+
>
98+
<Routeindexelement={<TemplateSummaryPage/>}/>
99+
<Routepath="collaborators"element={<TemplateCollaboratorsPage/>}/>
100+
</Route>
101+
<Route
102+
path="workspace"
103+
element={
104+
<RequireAuth>
105+
<CreateWorkspacePage/>
106+
</RequireAuth>
107+
}
96108
/>
97109
<Route
98110
path="workspace"

‎site/src/api/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,10 @@ export const getTemplateDAUs = async (
486486
constresponse=awaitaxios.get(`/api/v2/templates/${templateId}/daus`)
487487
returnresponse.data
488488
}
489+
490+
exportconstgetTemplateUserRoles=async(
491+
templateId:string,
492+
):Promise<TypesGen.TemplateUser[]>=>{
493+
constresponse=awaitaxios.get(`/api/v2/templates/${templateId}/user-roles`)
494+
returnresponse.data
495+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
importAvatarfrom"@material-ui/core/Avatar"
2+
importButtonfrom"@material-ui/core/Button"
3+
importLinkfrom"@material-ui/core/Link"
4+
import{makeStyles}from"@material-ui/core/styles"
5+
importAddCircleOutlinefrom"@material-ui/icons/AddCircleOutline"
6+
importSettingsOutlinedfrom"@material-ui/icons/SettingsOutlined"
7+
import{useMachine,useSelector}from"@xstate/react"
8+
import{DeleteDialog}from"components/Dialogs/DeleteDialog/DeleteDialog"
9+
import{DeleteButton}from"components/DropdownButton/ActionCtas"
10+
import{DropdownButton}from"components/DropdownButton/DropdownButton"
11+
import{Loader}from"components/Loader/Loader"
12+
import{PageHeader,PageHeaderSubtitle,PageHeaderTitle}from"components/PageHeader/PageHeader"
13+
import{useOrganizationId}from"hooks/useOrganizationId"
14+
import{FC,useContext}from"react"
15+
import{useTranslation}from"react-i18next"
16+
import{LinkasRouterLink,Navigate,NavLink,Outlet,useParams}from"react-router-dom"
17+
import{combineClasses}from"util/combineClasses"
18+
import{firstLetter}from"util/firstLetter"
19+
import{selectPermissions}from"xServices/auth/authSelectors"
20+
import{XServiceContext}from"xServices/StateContext"
21+
import{templateMachine}from"xServices/template/templateXService"
22+
import{Margins}from"../../components/Margins/Margins"
23+
import{Stack}from"../../components/Stack/Stack"
24+
25+
constuseTemplateName=()=>{
26+
const{ template}=useParams()
27+
28+
if(!template){
29+
thrownewError("No template found in the URL")
30+
}
31+
32+
returntemplate
33+
}
34+
35+
constLanguage={
36+
settingsButton:"Settings",
37+
createButton:"Create workspace",
38+
noDescription:"",
39+
}
40+
41+
exportconstTemplateLayout:FC=()=>{
42+
conststyles=useStyles()
43+
constorganizationId=useOrganizationId()
44+
consttemplateName=useTemplateName()
45+
const{ t}=useTranslation("templatePage")
46+
const[templateState,templateSend]=useMachine(templateMachine,{
47+
context:{
48+
templateName,
49+
organizationId,
50+
},
51+
})
52+
const{ template, activeTemplateVersion, templateResources, templateDAUs}=templateState.context
53+
constxServices=useContext(XServiceContext)
54+
constpermissions=useSelector(xServices.authXService,selectPermissions)
55+
constisLoading=
56+
!template||!activeTemplateVersion||!templateResources||!permissions||!templateDAUs
57+
58+
if(isLoading){
59+
return<Loader/>
60+
}
61+
62+
if(templateState.matches("deleted")){
63+
return<Navigateto="/templates"/>
64+
}
65+
66+
consthasIcon=template.icon&&template.icon!==""
67+
68+
constcreateWorkspaceButton=(className?:string)=>(
69+
<Linkunderline="none"component={RouterLink}to={`/templates/${template.name}/workspace`}>
70+
<ButtonclassName={className??""}startIcon={<AddCircleOutline/>}>
71+
{Language.createButton}
72+
</Button>
73+
</Link>
74+
)
75+
76+
consthandleDeleteTemplate=()=>{
77+
templateSend("DELETE")
78+
}
79+
80+
return(
81+
<>
82+
<Margins>
83+
<PageHeader
84+
actions={
85+
<>
86+
<Link
87+
underline="none"
88+
component={RouterLink}
89+
to={`/templates/${template.name}/settings`}
90+
>
91+
<Buttonvariant="outlined"startIcon={<SettingsOutlined/>}>
92+
{Language.settingsButton}
93+
</Button>
94+
</Link>
95+
96+
{permissions.deleteTemplates ?(
97+
<DropdownButton
98+
primaryAction={createWorkspaceButton(styles.actionButton)}
99+
secondaryActions={[
100+
{
101+
action:"delete",
102+
button:<DeleteButtonhandleAction={handleDeleteTemplate}/>,
103+
},
104+
]}
105+
canCancel={false}
106+
/>
107+
) :(
108+
createWorkspaceButton()
109+
)}
110+
</>
111+
}
112+
>
113+
<Stackdirection="row"spacing={3}className={styles.pageTitle}>
114+
<div>
115+
{hasIcon ?(
116+
<divclassName={styles.iconWrapper}>
117+
<imgsrc={template.icon}alt=""/>
118+
</div>
119+
) :(
120+
<AvatarclassName={styles.avatar}>{firstLetter(template.name)}</Avatar>
121+
)}
122+
</div>
123+
<div>
124+
<PageHeaderTitle>{template.name}</PageHeaderTitle>
125+
<PageHeaderSubtitlecondensed>
126+
{template.description==="" ?Language.noDescription :template.description}
127+
</PageHeaderSubtitle>
128+
</div>
129+
</Stack>
130+
</PageHeader>
131+
</Margins>
132+
133+
<divclassName={styles.tabs}>
134+
<Margins>
135+
<Stackdirection="row"spacing={0.25}>
136+
<NavLink
137+
end
138+
to={`/templates/${template.name}`}
139+
className={({ isActive})=>
140+
combineClasses([styles.tabItem,isActive ?styles.tabItemActive :undefined])
141+
}
142+
>
143+
Summary
144+
</NavLink>
145+
<NavLink
146+
to={`/templates/${template.name}/collaborators`}
147+
className={({ isActive})=>
148+
combineClasses([styles.tabItem,isActive ?styles.tabItemActive :undefined])
149+
}
150+
>
151+
Collaborators
152+
</NavLink>
153+
</Stack>
154+
</Margins>
155+
</div>
156+
157+
<Margins>
158+
<Outletcontext={{templateContext:templateState.context, permissions}}/>
159+
</Margins>
160+
161+
<DeleteDialog
162+
isOpen={templateState.matches("confirmingDelete")}
163+
confirmLoading={templateState.matches("deleting")}
164+
title={t("deleteDialog.title")}
165+
description={t("deleteDialog.description")}
166+
onConfirm={()=>{
167+
templateSend("CONFIRM_DELETE")
168+
}}
169+
onCancel={()=>{
170+
templateSend("CANCEL_DELETE")
171+
}}
172+
/>
173+
</>
174+
)
175+
}
176+
177+
exportconstuseStyles=makeStyles((theme)=>{
178+
return{
179+
actionButton:{
180+
border:"none",
181+
borderRadius:`${theme.shape.borderRadius}px 0px 0px${theme.shape.borderRadius}px`,
182+
},
183+
pageTitle:{
184+
alignItems:"center",
185+
},
186+
avatar:{
187+
width:theme.spacing(6),
188+
height:theme.spacing(6),
189+
fontSize:theme.spacing(3),
190+
},
191+
iconWrapper:{
192+
width:theme.spacing(6),
193+
height:theme.spacing(6),
194+
"& img":{
195+
width:"100%",
196+
},
197+
},
198+
199+
tabs:{
200+
borderBottom:`1px solid${theme.palette.divider}`,
201+
marginBottom:theme.spacing(5),
202+
},
203+
204+
tabItem:{
205+
textDecoration:"none",
206+
color:theme.palette.text.secondary,
207+
fontSize:14,
208+
display:"block",
209+
padding:theme.spacing(0,2,2),
210+
211+
"&:hover":{
212+
color:theme.palette.text.primary,
213+
},
214+
},
215+
216+
tabItemActive:{
217+
color:theme.palette.text.primary,
218+
position:"relative",
219+
220+
"&:before":{
221+
content:`""`,
222+
left:0,
223+
bottom:0,
224+
height:2,
225+
width:"100%",
226+
background:theme.palette.secondary.dark,
227+
position:"absolute",
228+
},
229+
},
230+
}
231+
})

‎site/src/hooks/useMe.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import{useSelector}from"@xstate/react"
2+
import{User}from"api/typesGenerated"
3+
import{useContext}from"react"
4+
import{selectUser}from"xServices/auth/authSelectors"
5+
import{XServiceContext}from"xServices/StateContext"
6+
7+
exportconstuseMe=():User=>{
8+
constxServices=useContext(XServiceContext)
9+
constme=useSelector(xServices.authXService,selectUser)
10+
11+
if(!me){
12+
thrownewError("User not found.")
13+
}
14+
15+
returnme
16+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import{useMachine}from"@xstate/react"
2+
import{useMe}from"hooks/useMe"
3+
import{FC}from"react"
4+
import{Helmet}from"react-helmet-async"
5+
import{useOutletContext}from"react-router-dom"
6+
import{pageTitle}from"util/page"
7+
import{Permissions}from"xServices/auth/authXService"
8+
import{templateUsersMachine}from"xServices/template/templateUsersXService"
9+
import{TemplateContext}from"xServices/template/templateXService"
10+
import{TemplateCollaboratorsPageView}from"./TemplateCollaboratorsPageView"
11+
12+
exportconstTemplateCollaboratorsPage:FC<React.PropsWithChildren<unknown>>=()=>{
13+
const{ templateContext, permissions}=useOutletContext<{
14+
templateContext:TemplateContext
15+
permissions:Permissions
16+
}>()
17+
const{ template, deleteTemplateError}=templateContext
18+
19+
if(!template){
20+
thrownewError(
21+
"This page should not be displayed until template, activeTemplateVersion or templateResources being loaded.",
22+
)
23+
}
24+
25+
const[state,send]=useMachine(templateUsersMachine,{context:{templateId:template.id}})
26+
const{ templateUsers, userToBeUpdated}=state.context
27+
constme=useMe()
28+
constuserTemplateRole=template.user_roles[me.id]
29+
constcanUpdatesUsers=
30+
permissions.deleteTemplates||userTemplateRole==="admin"||template.created_by_id===me.id
31+
32+
return(
33+
<>
34+
<Helmet>
35+
<title>{pageTitle(`${template.name} · Collaborators`)}</title>
36+
</Helmet>
37+
<TemplateCollaboratorsPageView
38+
canUpdateUsers={canUpdatesUsers}
39+
templateUsers={templateUsers}
40+
deleteTemplateError={deleteTemplateError}
41+
onAddUser={(user,role,reset)=>{
42+
send("ADD_USER",{ user, role,onDone:reset})
43+
}}
44+
isAddingUser={state.matches("addingUser")}
45+
onUpdateUser={(user,role)=>{
46+
send("UPDATE_USER_ROLE",{ user, role})
47+
}}
48+
updatingUser={userToBeUpdated}
49+
onRemoveUser={(user)=>{
50+
send("REMOVE_USER",{ user})
51+
}}
52+
/>
53+
</>
54+
)
55+
}
56+
57+
exportdefaultTemplateCollaboratorsPage

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp