Skip to content

Commit 8dfc683

Browse files
committed
feat(rbac): overhaul access control ui
1 parent 17cf055 commit 8dfc683

34 files changed

+5339
-1838
lines changed

frontend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@
107107
"papaparse": "^5.5.3",
108108
"posthog-js": "^1.268.8",
109109
"preact": "10.27.3",
110-
"react": "^18.3.1",
110+
"react": "^19.2.4",
111111
"react-colorful": "^5.6.1",
112112
"react-day-picker": "^9.11.1",
113-
"react-dom": "^18.3.1",
113+
"react-dom": "^19.2.4",
114114
"react-hook-form": "^7.63.0",
115115
"react-hotkeys-hook": "^5.1.0",
116116
"react-resizable-panels": "^1.0.10",

frontend/pnpm-lock.yaml

Lines changed: 702 additions & 714 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/app/organization/members/page.tsx

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,54 @@
11
"use client"
22

3+
import { ScopeGuard } from "@/components/auth/scope-guard"
34
import { OrgMembersTable } from "@/components/organization/org-members-table"
5+
import { OrgRbacGroups } from "@/components/organization/org-rbac-groups"
6+
import { OrgRbacRoles } from "@/components/organization/org-rbac-roles"
7+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
8+
9+
const tabTriggerClassName =
10+
"rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
411

512
export default function MembersPage() {
613
return (
714
<div className="size-full overflow-auto">
8-
<div className="container flex h-full max-w-[1000px] flex-col space-y-12">
15+
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
916
<div className="flex w-full">
1017
<div className="items-start space-y-3 text-left">
1118
<h2 className="text-2xl font-semibold tracking-tight">
12-
Organization members
19+
Members and access control
1320
</h2>
1421
<p className="text-md text-muted-foreground">
15-
View all organization members here.
22+
Manage organization members, roles, and groups.
1623
</p>
1724
</div>
18-
<div className="ml-auto flex items-center space-x-2"></div>
1925
</div>
20-
<div className="space-y-4">
21-
<>
22-
<h6 className="text-sm font-semibold">Manage members</h6>
26+
<Tabs defaultValue="members" className="w-full">
27+
<TabsList className="inline-flex h-auto w-auto justify-start gap-0 rounded-none border-b border-border/30 bg-transparent p-0">
28+
<TabsTrigger value="members" className={tabTriggerClassName}>
29+
Members
30+
</TabsTrigger>
31+
<ScopeGuard scope="org:rbac:read" fallback={null} loading={null}>
32+
<TabsTrigger value="roles" className={tabTriggerClassName}>
33+
Roles
34+
</TabsTrigger>
35+
<TabsTrigger value="groups" className={tabTriggerClassName}>
36+
Groups
37+
</TabsTrigger>
38+
</ScopeGuard>
39+
</TabsList>
40+
<TabsContent value="members" className="mt-6">
2341
<OrgMembersTable />
24-
</>
25-
</div>
42+
</TabsContent>
43+
<ScopeGuard scope="org:rbac:read" fallback={null} loading={null}>
44+
<TabsContent value="roles" className="mt-6">
45+
<OrgRbacRoles />
46+
</TabsContent>
47+
<TabsContent value="groups" className="mt-6">
48+
<OrgRbacGroups />
49+
</TabsContent>
50+
</ScopeGuard>
51+
</Tabs>
2652
</div>
2753
</div>
2854
)

frontend/src/app/organization/settings/rbac/page.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,37 @@ export default function RbacSettingsPage() {
3939
onValueChange={(v) => setActiveTab(v as RbacTab)}
4040
className="w-full"
4141
>
42-
<TabsList className="grid w-full max-w-[650px] grid-cols-5">
43-
<TabsTrigger value="roles">Roles</TabsTrigger>
44-
<TabsTrigger value="groups">Groups</TabsTrigger>
45-
<TabsTrigger value="assignments">Group assignments</TabsTrigger>
46-
<TabsTrigger value="user-assignments">User assignments</TabsTrigger>
47-
<TabsTrigger value="scopes">Scopes</TabsTrigger>
42+
<TabsList className="inline-flex h-auto w-auto justify-start gap-0 rounded-none border-b border-border/30 bg-transparent p-0">
43+
<TabsTrigger
44+
value="roles"
45+
className="rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
46+
>
47+
Roles
48+
</TabsTrigger>
49+
<TabsTrigger
50+
value="groups"
51+
className="rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
52+
>
53+
Groups
54+
</TabsTrigger>
55+
<TabsTrigger
56+
value="assignments"
57+
className="rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
58+
>
59+
Group assignments
60+
</TabsTrigger>
61+
<TabsTrigger
62+
value="user-assignments"
63+
className="rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
64+
>
65+
User assignments
66+
</TabsTrigger>
67+
<TabsTrigger
68+
value="scopes"
69+
className="rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
70+
>
71+
Scopes
72+
</TabsTrigger>
4873
</TabsList>
4974

5075
<TabsContent value="roles" className="mt-6">

frontend/src/app/workspaces/[workspaceId]/members/page.tsx

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
"use client"
22

3+
import { ScopeGuard } from "@/components/auth/scope-guard"
34
import { CenteredSpinner } from "@/components/loading/spinner"
45
import { AlertNotification } from "@/components/notifications"
6+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
57
import { WorkspaceMembersTable } from "@/components/workspaces/workspace-members-table"
8+
import { WorkspaceRbacGroups } from "@/components/workspaces/workspace-rbac-groups"
9+
import { WorkspaceRbacRoles } from "@/components/workspaces/workspace-rbac-roles"
610
import { useWorkspaceDetails } from "@/hooks/use-workspace"
711

12+
const tabTriggerClassName =
13+
"rounded-none border-b-2 border-transparent px-4 py-2.5 data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
14+
815
export default function WorkspaceMembersPage() {
916
const { workspace, workspaceLoading, workspaceError } = useWorkspaceDetails()
1017
if (workspaceLoading) {
@@ -23,10 +30,49 @@ export default function WorkspaceMembersPage() {
2330
}
2431
return (
2532
<div className="size-full overflow-auto">
26-
<div className="container flex h-full max-w-[1000px] flex-col space-y-8 py-8">
27-
<div className="space-y-4">
28-
<WorkspaceMembersTable workspace={workspace} />
33+
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
34+
<div className="flex w-full">
35+
<div className="items-start space-y-3 text-left">
36+
<h2 className="text-2xl font-semibold tracking-tight">Members</h2>
37+
<p className="text-md text-muted-foreground">
38+
Manage workspace members, roles, and groups.
39+
</p>
40+
</div>
2941
</div>
42+
<Tabs defaultValue="members" className="w-full">
43+
<TabsList className="inline-flex h-auto w-auto justify-start gap-0 rounded-none border-b border-border/30 bg-transparent p-0">
44+
<TabsTrigger value="members" className={tabTriggerClassName}>
45+
Members
46+
</TabsTrigger>
47+
<ScopeGuard
48+
scope="workspace:rbac:read"
49+
fallback={null}
50+
loading={null}
51+
>
52+
<TabsTrigger value="roles" className={tabTriggerClassName}>
53+
Roles
54+
</TabsTrigger>
55+
<TabsTrigger value="groups" className={tabTriggerClassName}>
56+
Groups
57+
</TabsTrigger>
58+
</ScopeGuard>
59+
</TabsList>
60+
<TabsContent value="members" className="mt-6">
61+
<WorkspaceMembersTable workspace={workspace} />
62+
</TabsContent>
63+
<ScopeGuard
64+
scope="workspace:rbac:read"
65+
fallback={null}
66+
loading={null}
67+
>
68+
<TabsContent value="roles" className="mt-6">
69+
<WorkspaceRbacRoles workspaceId={workspace.id} />
70+
</TabsContent>
71+
<TabsContent value="groups" className="mt-6">
72+
<WorkspaceRbacGroups workspaceId={workspace.id} />
73+
</TabsContent>
74+
</ScopeGuard>
75+
</Tabs>
3076
</div>
3177
</div>
3278
)

frontend/src/client/services.gen.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ import type {
539539
TriggersUpdateCaseTriggerResponse,
540540
TriggersUpdateWebhookData,
541541
TriggersUpdateWebhookResponse,
542+
UsersGetMyScopesData,
542543
UsersGetMyScopesResponse,
543544
UsersSearchUserData,
544545
UsersSearchUserResponse,
@@ -841,13 +842,14 @@ export const publicReceiveInteraction = (
841842

842843
/**
843844
* List Workspaces
844-
* List workspaces.
845+
* List workspaces the user has access to.
845846
*
846-
* Access Level
847-
* ------------
848-
* - Basic: Can list workspaces where they are a member.
849-
* - Admin: Can list all workspaces regardless of membership.
850-
* - Org Owner/Admin: Can list all workspaces in the organization.
847+
* Access
848+
* ------
849+
* - Org owners/admins (have `org:read` scope): See all workspaces in the org.
850+
* - Other users: See only workspaces where they are a member.
851+
*
852+
* No scope requirement - membership itself is the authorization.
851853
* @returns WorkspaceReadMinimal Successful Response
852854
* @throws ApiError
853855
*/
@@ -8454,13 +8456,20 @@ export const vcsGetGithubAppCredentialsStatus =
84548456
* @returns UserScopesRead Successful Response
84558457
* @throws ApiError
84568458
*/
8457-
export const usersGetMyScopes =
8458-
(): CancelablePromise<UsersGetMyScopesResponse> => {
8459-
return __request(OpenAPI, {
8460-
method: "GET",
8461-
url: "/users/me/scopes",
8462-
})
8463-
}
8459+
export const usersGetMyScopes = (
8460+
data: UsersGetMyScopesData = {}
8461+
): CancelablePromise<UsersGetMyScopesResponse> => {
8462+
return __request(OpenAPI, {
8463+
method: "GET",
8464+
url: "/users/me/scopes",
8465+
query: {
8466+
workspace_id: data.workspaceId,
8467+
},
8468+
errors: {
8469+
422: "Validation Error",
8470+
},
8471+
})
8472+
}
84648473

84658474
/**
84668475
* List Scopes

frontend/src/client/types.gen.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8928,6 +8928,13 @@ export type VcsDeleteGithubAppCredentialsResponse = void
89288928
export type VcsGetGithubAppCredentialsStatusResponse =
89298929
GitHubAppCredentialsStatus
89308930

8931+
export type UsersGetMyScopesData = {
8932+
/**
8933+
* Workspace to get scopes for
8934+
*/
8935+
workspaceId?: string | null
8936+
}
8937+
89318938
export type UsersGetMyScopesResponse = UserScopesRead
89328939

89338940
export type RbacListScopesData = {
@@ -13326,11 +13333,16 @@ export type $OpenApiTs = {
1332613333
}
1332713334
"/users/me/scopes": {
1332813335
get: {
13336+
req: UsersGetMyScopesData
1332913337
res: {
1333013338
/**
1333113339
* Successful Response
1333213340
*/
1333313341
200: UserScopesRead
13342+
/**
13343+
* Validation Error
13344+
*/
13345+
422: HTTPValidationError
1333413346
}
1333513347
}
1333613348
}

0 commit comments

Comments
 (0)