RBAC Dashboard Design

Overview

The Role-Based Access Control (RBAC) dashboard is a comprehensive interface within do.dev for managing users, roles, permissions, organizations, and teams. All new users start with a "waitlist" role and must be approved by administrators.

Dashboard Architecture

graph TB
    subgraph "RBAC Dashboard"
        MAIN[RBAC Main View]
        WAITLIST[Waitlist Management]
        ROLES[Role Management]
        PERMS[Permission Matrix]
        USERS[User Management]
        ORGS[Organization Management]
        TEAMS[Team Management]
        AUDIT[Audit Logs]
    end
    
    subgraph "Core Features"
        APPROVE[Approve/Reject Users]
        CREATE[Create Roles]
        ASSIGN[Assign Permissions]
        INVITE[Invite Members]
        MONITOR[Monitor Activity]
    end
    
    MAIN --> WAITLIST
    MAIN --> ROLES
    MAIN --> PERMS
    MAIN --> USERS
    MAIN --> ORGS
    MAIN --> TEAMS
    MAIN --> AUDIT
    
    WAITLIST --> APPROVE
    ROLES --> CREATE
    PERMS --> ASSIGN
    ORGS --> INVITE
    AUDIT --> MONITOR

UI Components

1. Waitlist Management

// Waitlist dashboard component
export function WaitlistDashboard() {
  return (
    <div className="space-y-6">
      <WaitlistStats />
      <WaitlistQueue />
      <BulkActions />
      <ApprovalHistory />
    </div>
  )
}

// Waitlist statistics
export function WaitlistStats() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
      <StatCard
        title="Pending Users"
        value={pendingCount}
        trend="+12% from last week"
        icon={<UserPlus />}
      />
      <StatCard
        title="Approved Today"
        value={approvedToday}
        trend="5 users"
        icon={<CheckCircle />}
      />
      <StatCard
        title="Rejected"
        value={rejectedCount}
        trend="2 users"
        icon={<XCircle />}
      />
      <StatCard
        title="Avg Wait Time"
        value="2.5 days"
        trend="-0.5 days"
        icon={<Clock />}
      />
    </div>
  )
}

// Waitlist queue with filtering
export function WaitlistQueue() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Waitlist Queue</CardTitle>
        <div className="flex gap-2">
          <SearchInput placeholder="Search users..." />
          <FilterDropdown
            options={[
              { label: "All", value: "all" },
              { label: "New Today", value: "today" },
              { label: "This Week", value: "week" },
              { label: "Has Organization", value: "org" },
            ]}
          />
          <SortDropdown
            options={[
              { label: "Newest First", value: "-created" },
              { label: "Oldest First", value: "created" },
              { label: "By Email", value: "email" },
            ]}
          />
        </div>
      </CardHeader>
      <CardContent>
        <WaitlistTable />
      </CardContent>
    </Card>
  )
}

// Individual waitlist entry
interface WaitlistEntry {
  id: string
  email: string
  name: string
  createdAt: Date
  source: string
  referrer?: string
  notes?: string
  riskScore: number
  emailVerified: boolean
}

// Waitlist table with actions
export function WaitlistTable() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>
            <Checkbox /> {/* Select all */}
          </TableHead>
          <TableHead>User</TableHead>
          <TableHead>Registration</TableHead>
          <TableHead>Source</TableHead>
          <TableHead>Risk Score</TableHead>
          <TableHead>Actions</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {waitlistUsers.map((user) => (
          <TableRow key={user.id}>
            <TableCell>
              <Checkbox />
            </TableCell>
            <TableCell>
              <div className="flex items-center gap-3">
                <Avatar>
                  <AvatarImage src={user.avatar} />
                  <AvatarFallback>{getInitials(user.name)}</AvatarFallback>
                </Avatar>
                <div>
                  <div className="font-medium">{user.name}</div>
                  <div className="text-sm text-muted-foreground">{user.email}</div>
                  {!user.emailVerified && (
                    <Badge variant="warning" size="sm">Unverified</Badge>
                  )}
                </div>
              </div>
            </TableCell>
            <TableCell>
              <div className="text-sm">
                <div>{formatDate(user.createdAt)}</div>
                <div className="text-muted-foreground">
                  {formatRelativeTime(user.createdAt)}
                </div>
              </div>
            </TableCell>
            <TableCell>
              <Badge variant="outline">{user.source}</Badge>
              {user.referrer && (
                <div className="text-xs text-muted-foreground mt-1">
                  via {user.referrer}
                </div>
              )}
            </TableCell>
            <TableCell>
              <RiskScoreBadge score={user.riskScore} />
            </TableCell>
            <TableCell>
              <div className="flex gap-2">
                <Button
                  size="sm"
                  variant="default"
                  onClick={() => approveUser(user.id)}
                >
                  Approve
                </Button>
                <Button
                  size="sm"
                  variant="outline"
                  onClick={() => viewUserDetails(user.id)}
                >
                  View
                </Button>
                <DropdownMenu>
                  <DropdownMenuTrigger asChild>
                    <Button size="sm" variant="ghost">
                      <MoreHorizontal className="h-4 w-4" />
                    </Button>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent>
                    <DropdownMenuItem onClick={() => addNote(user.id)}>
                      Add Note
                    </DropdownMenuItem>
                    <DropdownMenuItem onClick={() => requestMoreInfo(user.id)}>
                      Request Info
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem
                      onClick={() => rejectUser(user.id)}
                      className="text-destructive"
                    >
                      Reject
                    </DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
              </div>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}

2. Role Management Interface

// Role management dashboard
export function RoleManagementDashboard() {
  return (
    <div className="space-y-6">
      <RoleOverview />
      <RoleHierarchy />
      <RolesList />
      <PermissionAssignment />
    </div>
  )
}

// Visual role hierarchy
export function RoleHierarchy() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Role Hierarchy</CardTitle>
        <CardDescription>
          Visual representation of role inheritance and permissions
        </CardDescription>
      </CardHeader>
      <CardContent>
        <div className="relative">
          {/* Interactive tree view of roles */}
          <RoleTree
            roles={roles}
            onRoleClick={(role) => setSelectedRole(role)}
            onPermissionDrop={(roleId, permissionId) => 
              assignPermission(roleId, permissionId)
            }
          />
        </div>
      </CardContent>
    </Card>
  )
}

// Role creation form
export function CreateRoleDialog() {
  return (
    <Dialog>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Create New Role</DialogTitle>
        </DialogHeader>
        <form onSubmit={handleCreateRole}>
          <div className="space-y-4">
            <div>
              <Label htmlFor="name">Role Name</Label>
              <Input
                id="name"
                placeholder="e.g., Content Editor"
                value={roleName}
                onChange={(e) => setRoleName(e.target.value)}
              />
            </div>
            
            <div>
              <Label htmlFor="description">Description</Label>
              <Textarea
                id="description"
                placeholder="Describe the role's purpose..."
                value={description}
                onChange={(e) => setDescription(e.target.value)}
              />
            </div>
            
            <div>
              <Label>Scope</Label>
              <RadioGroup value={scope} onValueChange={setScope}>
                <div className="flex items-center space-x-2">
                  <RadioGroupItem value="global" id="global" />
                  <Label htmlFor="global">Global (Platform-wide)</Label>
                </div>
                <div className="flex items-center space-x-2">
                  <RadioGroupItem value="organization" id="organization" />
                  <Label htmlFor="organization">Organization</Label>
                </div>
                <div className="flex items-center space-x-2">
                  <RadioGroupItem value="team" id="team" />
                  <Label htmlFor="team">Team</Label>
                </div>
              </RadioGroup>
            </div>
            
            <div>
              <Label>Inherits From</Label>
              <Select value={inheritsFrom} onValueChange={setInheritsFrom}>
                <SelectTrigger>
                  <SelectValue placeholder="Select parent role..." />
                </SelectTrigger>
                <SelectContent>
                  {availableParentRoles.map((role) => (
                    <SelectItem key={role.id} value={role.id}>
                      {role.name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            
            <div>
              <Label>Initial Permissions</Label>
              <PermissionPicker
                selectedPermissions={selectedPermissions}
                onChange={setSelectedPermissions}
              />
            </div>
          </div>
          
          <DialogFooter>
            <Button type="submit">Create Role</Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}

3. Permission Matrix

// Visual permission matrix
export function PermissionMatrix() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Permission Matrix</CardTitle>
        <div className="flex items-center gap-4">
          <SearchInput placeholder="Search permissions..." />
          <Button variant="outline" size="sm">
            <Download className="h-4 w-4 mr-2" />
            Export
          </Button>
        </div>
      </CardHeader>
      <CardContent>
        <div className="overflow-x-auto">
          <table className="w-full border-collapse">
            <thead>
              <tr>
                <th className="sticky left-0 bg-background p-2 text-left">
                  Resource
                </th>
                {roles.map((role) => (
                  <th
                    key={role.id}
                    className="p-2 text-center min-w-[100px] cursor-pointer hover:bg-muted"
                    onClick={() => selectRole(role.id)}
                  >
                    <div className="font-medium">{role.name}</div>
                    <div className="text-xs text-muted-foreground">
                      {role.userCount} users
                    </div>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {resources.map((resource) => (
                <tr key={resource.id}>
                  <td className="sticky left-0 bg-background p-2 font-medium">
                    {resource.name}
                  </td>
                  {roles.map((role) => (
                    <td key={`${resource.id}-${role.id}`} className="p-2">
                      <PermissionCell
                        resource={resource}
                        role={role}
                        permissions={getPermissions(resource.id, role.id)}
                        onChange={(permissions) =>
                          updatePermissions(resource.id, role.id, permissions)
                        }
                      />
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </CardContent>
    </Card>
  )
}

// Individual permission cell
export function PermissionCell({ resource, role, permissions, onChange }) {
  const actions = ['create', 'read', 'update', 'delete']
  
  return (
    <div className="flex justify-center gap-1">
      {actions.map((action) => (
        <Tooltip key={action}>
          <TooltipTrigger>
            <button
              className={cn(
                "w-6 h-6 rounded text-xs font-medium",
                permissions.includes(action)
                  ? "bg-green-500 text-white"
                  : "bg-gray-200 text-gray-400"
              )}
              onClick={() => togglePermission(action)}
            >
              {action[0].toUpperCase()}
            </button>
          </TooltipTrigger>
          <TooltipContent>
            {action} {resource.name}
          </TooltipContent>
        </Tooltip>
      ))}
    </div>
  )
}

4. Organization Management

// Organization dashboard
export function OrganizationManagementDashboard() {
  return (
    <div className="space-y-6">
      <OrganizationStats />
      <OrganizationList />
      <TeamOverview />
      <InvitationManager />
    </div>
  )
}

// Organization creation wizard
export function CreateOrganizationWizard() {
  const [step, setStep] = useState(1)
  
  return (
    <Card>
      <CardHeader>
        <CardTitle>Create Organization</CardTitle>
        <Progress value={(step / 4) * 100} />
      </CardHeader>
      <CardContent>
        {step === 1 && <BasicInfoStep />}
        {step === 2 && <PlanSelectionStep />}
        {step === 3 && <TeamSetupStep />}
        {step === 4 && <ReviewStep />}
      </CardContent>
      <CardFooter className="flex justify-between">
        <Button
          variant="outline"
          onClick={() => setStep(step - 1)}
          disabled={step === 1}
        >
          Previous
        </Button>
        <Button
          onClick={() => step === 4 ? createOrganization() : setStep(step + 1)}
        >
          {step === 4 ? 'Create Organization' : 'Next'}
        </Button>
      </CardFooter>
    </Card>
  )
}

// Organization member management
export function OrganizationMembers({ organizationId }) {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Organization Members</CardTitle>
        <div className="flex gap-2">
          <Button onClick={openInviteDialog}>
            <UserPlus className="h-4 w-4 mr-2" />
            Invite Members
          </Button>
          <Button variant="outline">
            <Upload className="h-4 w-4 mr-2" />
            Bulk Import
          </Button>
        </div>
      </CardHeader>
      <CardContent>
        <MemberTable
          members={members}
          onRoleChange={(memberId, newRole) =>
            updateMemberRole(organizationId, memberId, newRole)
          }
          onRemove={(memberId) =>
            removeMember(organizationId, memberId)
          }
        />
      </CardContent>
    </Card>
  )
}

5. Audit Log Interface

// Audit log viewer
export function AuditLogDashboard() {
  return (
    <div className="space-y-6">
      <AuditLogFilters />
      <AuditLogTimeline />
      <AuditLogDetails />
    </div>
  )
}

// Audit log entry
interface AuditLogEntry {
  id: string
  timestamp: Date
  actor: {
    id: string
    name: string
    email: string
    role: string
  }
  action: string
  resource: {
    type: string
    id: string
    name: string
  }
  changes?: {
    field: string
    oldValue: any
    newValue: any
  }[]
  ipAddress: string
  userAgent: string
  organizationId?: string
  teamId?: string
}

// Audit log timeline view
export function AuditLogTimeline() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Activity Timeline</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="space-y-4">
          {auditLogs.map((log, index) => (
            <div key={log.id} className="flex gap-4">
              <div className="flex flex-col items-center">
                <div className={cn(
                  "w-10 h-10 rounded-full flex items-center justify-center",
                  getActionColor(log.action)
                )}>
                  {getActionIcon(log.action)}
                </div>
                {index < auditLogs.length - 1 && (
                  <div className="w-0.5 h-full bg-gray-200 mt-2" />
                )}
              </div>
              <div className="flex-1 pb-8">
                <div className="flex items-center justify-between">
                  <div>
                    <p className="font-medium">
                      {log.actor.name} {getActionDescription(log)}
                    </p>
                    <p className="text-sm text-muted-foreground">
                      {formatRelativeTime(log.timestamp)}
                    </p>
                  </div>
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={() => viewLogDetails(log.id)}
                  >
                    View Details
                  </Button>
                </div>
                {log.changes && (
                  <div className="mt-2 p-3 bg-muted rounded-md">
                    <p className="text-sm font-medium mb-1">Changes:</p>
                    {log.changes.map((change, i) => (
                      <div key={i} className="text-sm">
                        <span className="text-muted-foreground">
                          {change.field}:
                        </span>{" "}
                        <span className="line-through text-red-600">
                          {formatValue(change.oldValue)}
                        </span>{" "}
                        →{" "}
                        <span className="text-green-600">
                          {formatValue(change.newValue)}
                        </span>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            </div>
          ))}
        </div>
      </CardContent>
    </Card>
  )
}

API Endpoints

Waitlist Management

// Waitlist API endpoints
POST   /api/admin/waitlist/approve
POST   /api/admin/waitlist/reject
POST   /api/admin/waitlist/bulk-approve
GET    /api/admin/waitlist
GET    /api/admin/waitlist/stats
POST   /api/admin/waitlist/:userId/note

Role Management

// Role CRUD operations
GET    /api/admin/roles
POST   /api/admin/roles
PUT    /api/admin/roles/:roleId
DELETE /api/admin/roles/:roleId

// Permission management
GET    /api/admin/permissions
POST   /api/admin/roles/:roleId/permissions
DELETE /api/admin/roles/:roleId/permissions/:permissionId

// User role assignment
POST   /api/admin/users/:userId/roles
DELETE /api/admin/users/:userId/roles/:roleId

Organization Management

// Organization operations
GET    /api/organizations
POST   /api/organizations
PUT    /api/organizations/:orgId
DELETE /api/organizations/:orgId

// Member management
GET    /api/organizations/:orgId/members
POST   /api/organizations/:orgId/invitations
PUT    /api/organizations/:orgId/members/:userId/role
DELETE /api/organizations/:orgId/members/:userId

// Team operations
GET    /api/organizations/:orgId/teams
POST   /api/organizations/:orgId/teams
PUT    /api/teams/:teamId
DELETE /api/teams/:teamId

Audit Logs

// Audit log queries
GET    /api/admin/audit-logs
GET    /api/admin/audit-logs/:logId
POST   /api/admin/audit-logs/export

Security Considerations

Access Control

  • Only super admins can access the full RBAC dashboard
  • Organization admins can manage their organization's roles and members
  • Team admins can manage team members
  • All actions are logged in the audit trail

Data Protection

  • Sensitive operations require re-authentication
  • IP-based restrictions for admin access
  • Rate limiting on bulk operations
  • Encrypted audit logs

Compliance

  • GDPR-compliant data handling
  • Role change notifications
  • Audit log retention policies
  • Data export capabilities

Performance Optimization

Caching Strategy

// Cache configuration
const cacheConfig = {
  roles: {
    ttl: 300, // 5 minutes
    key: (orgId?: string) => `roles:${orgId || 'global'}`
  },
  permissions: {
    ttl: 600, // 10 minutes
    key: (roleId: string) => `permissions:${roleId}`
  },
  waitlist: {
    ttl: 60, // 1 minute
    key: () => 'waitlist:pending'
  }
}

Pagination

All list views support pagination:

interface PaginationParams {
  page: number
  limit: number
  sort?: string
  filter?: Record<string, any>
}

interface PaginatedResponse<T> {
  data: T[]
  total: number
  page: number
  limit: number
  hasMore: boolean
}

Real-time Updates

// WebSocket events for real-time updates
const wsEvents = {
  'waitlist:user:added': (user: WaitlistUser) => void
  'waitlist:user:approved': (userId: string) => void
  'role:created': (role: Role) => void
  'role:updated': (role: Role) => void
  'permission:assigned': (data: { roleId: string, permission: Permission }) => void
  'organization:member:added': (data: { orgId: string, member: Member }) => void
}

Mobile Responsiveness

The RBAC dashboard is fully responsive:

  • Collapsible sidebar on mobile
  • Touch-friendly controls
  • Swipeable actions
  • Optimized data tables for small screens
  • Progressive disclosure of complex forms

Accessibility

  • WCAG 2.1 AA compliance
  • Keyboard navigation support
  • Screen reader announcements
  • High contrast mode support
  • Focus indicators
  • ARIA labels and descriptions

On this page