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

Commit7ceecef

Browse files
committed
Add stories for multi-org sidebar
1 parenta193adb commit7ceecef

File tree

3 files changed

+504
-343
lines changed

3 files changed

+504
-343
lines changed
Lines changed: 28 additions & 343 deletions
Original file line numberDiff line numberDiff line change
@@ -1,356 +1,41 @@
1-
import{cx}from"@emotion/css";
2-
importtype{Interpolation,Theme}from"@emotion/react";
3-
importAddIconfrom"@mui/icons-material/Add";
4-
importSettingsIconfrom"@mui/icons-material/Settings";
5-
importtype{FC,ReactNode}from"react";
1+
importtype{FC}from"react";
62
import{useQuery}from"react-query";
7-
import{Link,NavLink,useLocation,useParams}from"react-router-dom";
3+
import{useParams}from"react-router-dom";
84
import{organizationPermissions}from"api/queries/organizations";
9-
importtype{Organization}from"api/typesGenerated";
10-
import{Loader}from"components/Loader/Loader";
11-
import{SidebarasBaseSidebar}from"components/Sidebar/Sidebar";
12-
import{Stack}from"components/Stack/Stack";
13-
import{UserAvatar}from"components/UserAvatar/UserAvatar";
145
import{useAuthenticated}from"contexts/auth/RequireAuth";
15-
import{typeClassName,useClassName}from"hooks/useClassName";
16-
import{useFeatureVisibility}from"modules/dashboard/useFeatureVisibility";
17-
import{AUDIT_LINK,USERS_LINK,withFilter}from"modules/navigation";
186
import{useOrganizationSettings}from"./ManagementSettingsLayout";
19-
7+
import{SidebarView}from"./SidebarView";
8+
9+
/**
10+
* A combined deployment settings and organization menu.
11+
*
12+
* This should only be used with multi-org support. If multi-org support is
13+
* disabled or not licensed, this is the wrong sidebar to use. See
14+
* DeploySettingsPage/Sidebar instead.
15+
*/
2016
exportconstSidebar:FC=()=>{
2117
const{ permissions}=useAuthenticated();
2218
const{ organizations}=useOrganizationSettings();
23-
const{ organization}=useParams()as{organization?:string};
24-
const{multiple_organizations:organizationsEnabled}=
25-
useFeatureVisibility();
26-
27-
letorganizationName=organization;
28-
if(location.pathname==="/organizations"&&organizations){
29-
organizationName=getOrganizationNameByDefault(organizations);
30-
}
31-
32-
// TODO: Do something nice to scroll to the active org.
33-
34-
return(
35-
<BaseSidebar>
36-
{organizationsEnabled&&(
37-
<headercss={styles.sidebarHeader}>Deployment</header>
38-
)}
39-
<DeploymentSettingsNavigation
40-
organizationsEnabled={organizationsEnabled}
41-
/>
42-
{organizationsEnabled&&
43-
(organizations ?(
44-
<>
45-
<headercss={styles.sidebarHeader}>Organizations</header>
46-
{permissions.createOrganization&&(
47-
<SidebarNavItem
48-
active="auto"
49-
href="/organizations/new"
50-
icon={<AddIcon/>}
51-
>
52-
New organization
53-
</SidebarNavItem>
54-
)}
55-
{organizations.map((org)=>(
56-
<OrganizationSettingsNavigation
57-
key={org.id}
58-
organization={org}
59-
active={org.name===organizationName}
60-
/>
61-
))}
62-
</>
63-
) :(
64-
<Loader/>
65-
))}
66-
</BaseSidebar>
19+
const{organization:organizationName}=useParams()as{
20+
organization?:string;
21+
};
22+
23+
// If there is no organization name, the settings page will load, and it will
24+
// redirect to the default organization, so eventually there will always be an
25+
// organization name.
26+
constactiveOrganization=organizations?.find(
27+
(o)=>o.name===organizationName,
6728
);
68-
};
69-
70-
interfaceDeploymentSettingsNavigationProps{
71-
organizationsEnabled?:boolean;
72-
}
73-
74-
constDeploymentSettingsNavigation:FC<DeploymentSettingsNavigationProps>=({
75-
organizationsEnabled,
76-
})=>{
77-
constlocation=useLocation();
78-
constactive=location.pathname.startsWith("/deployment");
79-
const{ permissions}=useAuthenticated();
80-
81-
return(
82-
<divcss={{paddingBottom:12}}>
83-
<SidebarNavItem
84-
active={active}
85-
href={
86-
permissions.viewDeploymentValues
87-
?"/deployment/general"
88-
:"/deployment/workspace-proxies"
89-
}
90-
// 24px matches the width of the organization icons, and the component is smart enough
91-
// to keep the icon itself square. It looks too big if it's 24x24.
92-
icon={<SettingsIconcss={{width:24,height:20}}/>}
93-
>
94-
Deployment
95-
</SidebarNavItem>
96-
{active&&(
97-
<Stackspacing={0.5}css={{marginBottom:8,marginTop:8}}>
98-
{permissions.viewDeploymentValues&&(
99-
<SidebarNavSubItemhref="general">General</SidebarNavSubItem>
100-
)}
101-
{permissions.viewDeploymentValues&&(
102-
<SidebarNavSubItemhref="licenses">Licenses</SidebarNavSubItem>
103-
)}
104-
{permissions.editDeploymentValues&&(
105-
<SidebarNavSubItemhref="appearance">Appearance</SidebarNavSubItem>
106-
)}
107-
{permissions.viewDeploymentValues&&(
108-
<SidebarNavSubItemhref="userauth">
109-
User Authentication
110-
</SidebarNavSubItem>
111-
)}
112-
{permissions.viewDeploymentValues&&(
113-
<SidebarNavSubItemhref="external-auth">
114-
External Authentication
115-
</SidebarNavSubItem>
116-
)}
117-
{/* Not exposing this yet since token exchange is not finished yet.
118-
<SidebarNavSubItem href="oauth2-provider/ap>
119-
OAuth2 Applications
120-
</SidebarNavSubItem>*/}
121-
{permissions.viewDeploymentValues&&(
122-
<SidebarNavSubItemhref="network">Network</SidebarNavSubItem>
123-
)}
124-
{/* All users can view workspace regions. */}
125-
<SidebarNavSubItemhref="workspace-proxies">
126-
Workspace Proxies
127-
</SidebarNavSubItem>
128-
{permissions.viewDeploymentValues&&(
129-
<SidebarNavSubItemhref="security">Security</SidebarNavSubItem>
130-
)}
131-
{permissions.viewDeploymentValues&&(
132-
<SidebarNavSubItemhref="observability">
133-
Observability
134-
</SidebarNavSubItem>
135-
)}
136-
{permissions.viewAllUsers&&(
137-
<SidebarNavSubItemhref={USERS_LINK.slice(1)}>
138-
Users
139-
</SidebarNavSubItem>
140-
)}
141-
{permissions.viewAnyGroup&&!organizationsEnabled&&(
142-
<SidebarNavSubItemhref="groups">Groups</SidebarNavSubItem>
143-
)}
144-
{permissions.viewAnyAuditLog&&(
145-
<SidebarNavSubItemhref={AUDIT_LINK.slice(1)}>
146-
Auditing
147-
</SidebarNavSubItem>
148-
)}
149-
</Stack>
150-
)}
151-
</div>
152-
);
153-
};
154-
155-
functionurlForSubpage(organizationName:string,subpage:string=""):string{
156-
return`/organizations/${organizationName}/${subpage}`;
157-
}
158-
159-
interfaceOrganizationSettingsNavigationProps{
160-
organization:Organization;
161-
active:boolean;
162-
}
163-
164-
exportconstOrganizationSettingsNavigation:FC<
165-
OrganizationSettingsNavigationProps
166-
>=({ organization, active})=>{
167-
// The menu items only show while the menu is expanded, so we can wait until
168-
// expanded to run the query. Downside is that we have to show a loader
169-
// until the query finishes...maybe we should prefetch permissions for all
170-
// the orgs instead?
171-
constpermissionsQuery=useQuery(
172-
organizationPermissions(active ?organization.id :undefined),
29+
constactiveOrgPermissionsQuery=useQuery(
30+
organizationPermissions(activeOrganization?.id),
17331
);
17432

17533
return(
176-
<>
177-
<SidebarNavItem
178-
active={active}
179-
href={urlForSubpage(organization.name)}
180-
icon={
181-
<UserAvatar
182-
key={organization.id}
183-
size="sm"
184-
username={organization.display_name}
185-
avatarURL={organization.icon}
186-
/>
187-
}
188-
>
189-
{organization.display_name}
190-
</SidebarNavItem>
191-
{active&&!permissionsQuery.data&&<Loader/>}
192-
{active&&permissionsQuery.data&&(
193-
<Stackspacing={0.5}css={{marginBottom:8,marginTop:8}}>
194-
{permissionsQuery.data.editOrganization&&(
195-
<SidebarNavSubItemendhref={urlForSubpage(organization.name)}>
196-
Organization settings
197-
</SidebarNavSubItem>
198-
)}
199-
{permissionsQuery.data.viewMembers&&(
200-
<SidebarNavSubItem
201-
href={urlForSubpage(organization.name,"members")}
202-
>
203-
Members
204-
</SidebarNavSubItem>
205-
)}
206-
{permissionsQuery.data.viewGroups&&(
207-
<SidebarNavSubItem
208-
href={urlForSubpage(organization.name,"groups")}
209-
>
210-
Groups
211-
</SidebarNavSubItem>
212-
)}
213-
{/* For now redirect to the site-wide audit page with the organization
214-
pre-filled into the filter. Based on user feedback we might want
215-
to serve a copy of the audit page or even delete this link. */}
216-
{permissionsQuery.data.auditOrganization&&(
217-
<SidebarNavSubItem
218-
href={`/deployment${withFilter(
219-
AUDIT_LINK,
220-
`organization:${organization.name}`,
221-
)}`}
222-
>
223-
Auditing
224-
</SidebarNavSubItem>
225-
)}
226-
</Stack>
227-
)}
228-
</>
34+
<SidebarView
35+
activeOrganization={activeOrganization}
36+
activeOrgPermissions={activeOrgPermissionsQuery.data}
37+
organizations={organizations}
38+
permissions={permissions}
39+
/>
22940
);
23041
};
231-
232-
interfaceSidebarNavItemProps{
233-
active?:boolean|"auto";
234-
children?:ReactNode;
235-
icon?:ReactNode;
236-
href:string;
237-
}
238-
239-
exportconstSidebarNavItem:FC<SidebarNavItemProps>=({
240-
active,
241-
children,
242-
href,
243-
icon,
244-
})=>{
245-
constlink=useClassName(classNames.link,[]);
246-
constactiveLink=useClassName(classNames.activeLink,[]);
247-
248-
constcontent=(
249-
<StackalignItems="center"spacing={1.5}direction="row">
250-
{icon}
251-
{children}
252-
</Stack>
253-
);
254-
255-
if(active==="auto"){
256-
return(
257-
<NavLink
258-
to={href}
259-
className={({ isActive})=>cx([link,isActive&&activeLink])}
260-
>
261-
{content}
262-
</NavLink>
263-
);
264-
}
265-
266-
return(
267-
<Linkto={href}className={cx([link,active&&activeLink])}>
268-
{content}
269-
</Link>
270-
);
271-
};
272-
273-
interfaceSidebarNavSubItemProps{
274-
children?:ReactNode;
275-
href:string;
276-
end?:boolean;
277-
}
278-
279-
exportconstSidebarNavSubItem:FC<SidebarNavSubItemProps>=({
280-
children,
281-
href,
282-
end,
283-
})=>{
284-
constlink=useClassName(classNames.subLink,[]);
285-
constactiveLink=useClassName(classNames.activeSubLink,[]);
286-
287-
return(
288-
<NavLink
289-
end={end}
290-
to={href}
291-
className={({ isActive})=>cx([link,isActive&&activeLink])}
292-
>
293-
{children}
294-
</NavLink>
295-
);
296-
};
297-
298-
conststyles={
299-
sidebarHeader:{
300-
textTransform:"uppercase",
301-
letterSpacing:"0.15em",
302-
fontSize:11,
303-
fontWeight:500,
304-
paddingBottom:4,
305-
},
306-
}satisfiesRecord<string,Interpolation<Theme>>;
307-
308-
constclassNames={
309-
link:(css,theme)=>css`
310-
color: inherit;
311-
display: block;
312-
font-size:14px;
313-
text-decoration: none;
314-
padding:10px12px10px16px;
315-
border-radius:4px;
316-
transition: background-color0.15s ease-in-out;
317-
position: relative;
318-
319-
&:hover {
320-
background-color:${theme.palette.action.hover};
321-
}
322-
323-
border-left:3px solid transparent;
324-
`,
325-
326-
activeLink:(css,theme)=>css`
327-
border-left-color:${theme.palette.primary.main};
328-
border-top-left-radius:0;
329-
border-bottom-left-radius:0;
330-
`,
331-
332-
subLink:(css,theme)=>css`
333-
color: inherit;
334-
text-decoration: none;
335-
336-
display: block;
337-
font-size:13px;
338-
margin-left:44px;
339-
padding:4px12px;
340-
border-radius:4px;
341-
transition: background-color0.15s ease-in-out;
342-
margin-bottom:1px;
343-
position: relative;
344-
345-
&:hover {
346-
background-color:${theme.palette.action.hover};
347-
}
348-
`,
349-
350-
activeSubLink:(css)=>css`
351-
font-weight:600;
352-
`,
353-
}satisfiesRecord<string,ClassName>;
354-
355-
constgetOrganizationNameByDefault=(organizations:Organization[])=>
356-
organizations.find((org)=>org.is_default)?.name;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp