Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@
"papaparse": "^5.5.3",
"posthog-js": "^1.268.8",
"preact": "10.27.3",
"react": "^18.3.1",
"react": "^19.2.4",
"react-colorful": "^5.6.1",
"react-day-picker": "^9.11.1",
"react-dom": "^18.3.1",
"react-dom": "^19.2.4",
"react-hook-form": "^7.63.0",
"react-hotkeys-hook": "^5.1.0",
"react-resizable-panels": "^1.0.10",
Expand Down
1,416 changes: 702 additions & 714 deletions frontend/pnpm-lock.yaml

Large diffs are not rendered by default.

23 changes: 13 additions & 10 deletions frontend/src/app/organization/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AuthGuard } from "@/components/auth/auth-guard"
import { CenteredSpinner } from "@/components/loading/spinner"
import { OrganizationSidebar } from "@/components/sidebar/organization-sidebar"
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
import { ScopeProvider } from "@/providers/scopes"

export default function OrganizationLayout({
children,
Expand All @@ -13,18 +14,20 @@ export default function OrganizationLayout({
}) {
return (
<AuthGuard requireAuth requireOrgAdmin>
<SidebarProvider>
<OrganizationSidebar />
<SidebarInset>
<div className="flex h-full flex-1 flex-col">
<div className="flex-1 overflow-auto">
<div className="container py-16">
<Suspense fallback={<CenteredSpinner />}>{children}</Suspense>
<ScopeProvider>
<SidebarProvider>
<OrganizationSidebar />
<SidebarInset>
<div className="flex h-full flex-1 flex-col">
<div className="flex-1 overflow-auto">
<div className="container py-16">
<Suspense fallback={<CenteredSpinner />}>{children}</Suspense>
</div>
</div>
</div>
</div>
</SidebarInset>
</SidebarProvider>
</SidebarInset>
</SidebarProvider>
</ScopeProvider>
</AuthGuard>
)
}
44 changes: 35 additions & 9 deletions frontend/src/app/organization/members/page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,54 @@
"use client"

import { ScopeGuard } from "@/components/auth/scope-guard"
import { OrgMembersTable } from "@/components/organization/org-members-table"
import { OrgRbacGroups } from "@/components/organization/org-rbac-groups"
import { OrgRbacRoles } from "@/components/organization/org-rbac-roles"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

const tabTriggerClassName =
"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"

export default function MembersPage() {
return (
<div className="size-full overflow-auto">
<div className="container flex h-full max-w-[1000px] flex-col space-y-12">
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
<div className="flex w-full">
<div className="items-start space-y-3 text-left">
<h2 className="text-2xl font-semibold tracking-tight">
Organization members
Members and access control
</h2>
<p className="text-md text-muted-foreground">
View all organization members here.
Manage organization members, roles, and groups.
</p>
</div>
<div className="ml-auto flex items-center space-x-2"></div>
</div>
<div className="space-y-4">
<>
<h6 className="text-sm font-semibold">Manage members</h6>
<Tabs defaultValue="members" className="w-full">
<TabsList className="inline-flex h-auto w-auto justify-start gap-0 rounded-none border-b border-border/30 bg-transparent p-0">
<TabsTrigger value="members" className={tabTriggerClassName}>
Members
</TabsTrigger>
<ScopeGuard scope="org:rbac:read" fallback={null} loading={null}>
<TabsTrigger value="roles" className={tabTriggerClassName}>
Roles
</TabsTrigger>
<TabsTrigger value="groups" className={tabTriggerClassName}>
Groups
</TabsTrigger>
</ScopeGuard>
</TabsList>
<TabsContent value="members" className="mt-6">
<OrgMembersTable />
</>
</div>
</TabsContent>
<ScopeGuard scope="org:rbac:read" fallback={null} loading={null}>
<TabsContent value="roles" className="mt-6">
<OrgRbacRoles />
</TabsContent>
<TabsContent value="groups" className="mt-6">
<OrgRbacGroups />
</TabsContent>
</ScopeGuard>
</Tabs>
</div>
</div>
)
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/app/organization/settings/rbac/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Metadata } from "next"

export const metadata: Metadata = {
title: "Access control | Organization",
}

export default function RbacLayout({
children,
}: {
children: React.ReactNode
}) {
return children
}
98 changes: 98 additions & 0 deletions frontend/src/app/organization/settings/rbac/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client"

import { useState } from "react"
import { OrgRbacAssignments } from "@/components/organization/org-rbac-assignments"
import { OrgRbacGroups } from "@/components/organization/org-rbac-groups"
import { OrgRbacRoles } from "@/components/organization/org-rbac-roles"
import { OrgRbacScopes } from "@/components/organization/org-rbac-scopes"
import { OrgRbacUserAssignments } from "@/components/organization/org-rbac-user-assignments"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

type RbacTab =
| "roles"
| "groups"
| "scopes"
| "assignments"
| "user-assignments"

export default function RbacSettingsPage() {
const [activeTab, setActiveTab] = useState<RbacTab>("roles")

return (
<div className="size-full overflow-auto">
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
<div className="flex w-full">
<div className="items-start space-y-3 text-left">
<h2 className="text-2xl font-semibold tracking-tight">
Access control
</h2>
<p className="text-md text-muted-foreground">
Manage roles, groups, and permissions for your organization.
Configure fine-grained access control with scopes and assign
permissions to users and groups.
</p>
</div>
</div>

<Tabs
value={activeTab}
onValueChange={(v) => setActiveTab(v as RbacTab)}
className="w-full"
>
<TabsList className="inline-flex h-auto w-auto justify-start gap-0 rounded-none border-b border-border/30 bg-transparent p-0">
<TabsTrigger
value="roles"
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"
>
Roles
</TabsTrigger>
<TabsTrigger
value="groups"
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"
>
Groups
</TabsTrigger>
<TabsTrigger
value="assignments"
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"
>
Group assignments
</TabsTrigger>
<TabsTrigger
value="user-assignments"
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"
>
User assignments
</TabsTrigger>
<TabsTrigger
value="scopes"
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"
>
Scopes
</TabsTrigger>
</TabsList>

<TabsContent value="roles" className="mt-6">
<OrgRbacRoles />
</TabsContent>

<TabsContent value="groups" className="mt-6">
<OrgRbacGroups />
</TabsContent>

<TabsContent value="assignments" className="mt-6">
<OrgRbacAssignments />
</TabsContent>

<TabsContent value="user-assignments" className="mt-6">
<OrgRbacUserAssignments />
</TabsContent>

<TabsContent value="scopes" className="mt-6">
<OrgRbacScopes />
</TabsContent>
</Tabs>
</div>
</div>
)
}
42 changes: 42 additions & 0 deletions frontend/src/app/workspaces/[workspaceId]/members/groups/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client"

import { ScopeGuard } from "@/components/auth/scope-guard"
import { CenteredSpinner } from "@/components/loading/spinner"
import { AlertNotification } from "@/components/notifications"
import { WorkspaceRbacGroups } from "@/components/workspaces/workspace-rbac-groups"
import { useWorkspaceDetails } from "@/hooks/use-workspace"

export default function WorkspaceGroupsPage() {
const { workspace, workspaceLoading, workspaceError } = useWorkspaceDetails()
if (workspaceLoading) {
return <CenteredSpinner />
}
if (workspaceError) {
return (
<AlertNotification
level="error"
message="Error loading workspace info."
/>
)
}
if (!workspace) {
return <AlertNotification level="error" message="Workspace not found." />
}
return (
<ScopeGuard scope="workspace:rbac:read" fallback={null} loading={null}>
<div className="size-full overflow-auto">
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
<div className="flex w-full">
<div className="items-start space-y-3 text-left">
<h2 className="text-2xl font-semibold tracking-tight">Groups</h2>
<p className="text-md text-muted-foreground">
Manage workspace groups and their members.
</p>
</div>
</div>
<WorkspaceRbacGroups workspaceId={workspace.id} hideCreateButton />
</div>
</div>
</ScopeGuard>
)
}
12 changes: 9 additions & 3 deletions frontend/src/app/workspaces/[workspaceId]/members/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ export default function WorkspaceMembersPage() {
}
return (
<div className="size-full overflow-auto">
<div className="container flex h-full max-w-[1000px] flex-col space-y-8 py-8">
<div className="space-y-4">
<WorkspaceMembersTable workspace={workspace} />
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
<div className="flex w-full">
<div className="items-start space-y-3 text-left">
<h2 className="text-2xl font-semibold tracking-tight">Members</h2>
<p className="text-md text-muted-foreground">
Manage workspace members, roles, and groups.
</p>
</div>
</div>
<WorkspaceMembersTable workspace={workspace} />
</div>
</div>
)
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/app/workspaces/[workspaceId]/members/roles/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client"

import { ScopeGuard } from "@/components/auth/scope-guard"
import { CenteredSpinner } from "@/components/loading/spinner"
import { AlertNotification } from "@/components/notifications"
import { WorkspaceRbacRoles } from "@/components/workspaces/workspace-rbac-roles"
import { useWorkspaceDetails } from "@/hooks/use-workspace"

export default function WorkspaceRolesPage() {
const { workspace, workspaceLoading, workspaceError } = useWorkspaceDetails()
if (workspaceLoading) {
return <CenteredSpinner />
}
if (workspaceError) {
return (
<AlertNotification
level="error"
message="Error loading workspace info."
/>
)
}
if (!workspace) {
return <AlertNotification level="error" message="Workspace not found." />
}
return (
<ScopeGuard scope="workspace:rbac:read" fallback={null} loading={null}>
<div className="size-full overflow-auto">
<div className="container flex h-full max-w-[1200px] flex-col space-y-8 py-6">
<div className="flex w-full">
<div className="items-start space-y-3 text-left">
<h2 className="text-2xl font-semibold tracking-tight">Roles</h2>
<p className="text-md text-muted-foreground">
Manage workspace roles and permissions.
</p>
</div>
</div>
<WorkspaceRbacRoles workspaceId={workspace.id} hideCreateButton />
</div>
</div>
</ScopeGuard>
)
}
18 changes: 12 additions & 6 deletions frontend/src/app/workspaces/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useAuth, useAuthActions } from "@/hooks/use-auth"
import { useOrgMembership } from "@/hooks/use-org-membership"
import { useWorkspaceManager } from "@/lib/hooks"
import { WorkflowBuilderProvider } from "@/providers/builder"
import { ScopeProvider } from "@/providers/scopes"
import { WorkflowProvider } from "@/providers/workflow"
import { WorkspaceIdProvider } from "@/providers/workspace-id"

Expand Down Expand Up @@ -77,13 +78,18 @@ export default function WorkspaceLayout({

return (
<WorkspaceIdProvider workspaceId={selectedWorkspaceId}>
{workflowId ? (
<WorkflowView workspaceId={selectedWorkspaceId} workflowId={workflowId}>
<ScopeProvider>
{workflowId ? (
<WorkflowView
workspaceId={selectedWorkspaceId}
workflowId={workflowId}
>
<WorkspaceChildren>{children}</WorkspaceChildren>
</WorkflowView>
) : (
<WorkspaceChildren>{children}</WorkspaceChildren>
</WorkflowView>
) : (
<WorkspaceChildren>{children}</WorkspaceChildren>
)}
)}
</ScopeProvider>
</WorkspaceIdProvider>
)
}
Expand Down
Loading
Loading