diff --git a/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx b/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx index 620e93803..a93cb87c6 100644 --- a/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx +++ b/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx @@ -1,13 +1,20 @@ import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema"; -import { Loader2, PlusIcon, ShieldCheck, TrashIcon, Users } from "lucide-react"; +import { + Loader2, + PlusIcon, + ShieldCheck, + Sparkles, + TrashIcon, + Users, +} from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { DialogAction } from "@/components/shared/dialog-action"; import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate"; -import { Button } from "@/components/ui/button"; import { AlertBlock } from "@/components/shared/alert-block"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -24,11 +31,6 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; import { Form, FormControl, @@ -38,6 +40,11 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; @@ -407,6 +414,114 @@ const ACTION_META: Record< /** Resources that should be hidden from the custom role editor (better-auth internals) */ const HIDDEN_RESOURCES = ["organization", "invitation", "team", "ac"]; +/** Predefined role presets with sensible permission defaults */ +const ROLE_PRESETS: { + name: string; + label: string; + description: string; + permissions: Record; +}[] = [ + { + name: "viewer", + label: "Viewer", + description: "Read-only access across all resources", + permissions: { + service: ["read"], + environment: ["read"], + docker: ["read"], + sshKeys: ["read"], + gitProviders: ["read"], + traefikFiles: ["read"], + api: ["read"], + volume: ["read"], + deployment: ["read"], + envVars: ["read"], + projectEnvVars: ["read"], + environmentEnvVars: ["read"], + server: ["read"], + registry: ["read"], + certificate: ["read"], + backup: ["read"], + volumeBackup: ["read"], + schedule: ["read"], + domain: ["read"], + destination: ["read"], + notification: ["read"], + member: ["read"], + logs: ["read"], + monitoring: ["read"], + auditLog: ["read"], + }, + }, + { + name: "developer", + label: "Developer", + description: "Deploy services, manage env vars, domains, and view logs", + permissions: { + project: ["create"], + service: ["create", "read"], + environment: ["create", "read"], + docker: ["read"], + gitProviders: ["read"], + api: ["read"], + volume: ["read", "create", "delete"], + deployment: ["read", "create", "cancel"], + envVars: ["read", "write"], + projectEnvVars: ["read"], + environmentEnvVars: ["read"], + domain: ["read", "create", "delete"], + schedule: ["read", "create", "update", "delete"], + logs: ["read"], + monitoring: ["read"], + }, + }, + { + name: "deployer", + label: "Deployer", + description: "Trigger and manage deployments only", + permissions: { + service: ["read"], + environment: ["read"], + deployment: ["read", "create", "cancel"], + logs: ["read"], + monitoring: ["read"], + }, + }, + { + name: "devops", + label: "DevOps", + description: + "Full infrastructure access: servers, registries, certs, backups, and deployments", + permissions: { + project: ["create", "delete"], + service: ["create", "read", "delete"], + environment: ["create", "read", "delete"], + docker: ["read"], + sshKeys: ["read", "create", "delete"], + gitProviders: ["read", "create", "delete"], + traefikFiles: ["read", "write"], + api: ["read"], + volume: ["read", "create", "delete"], + deployment: ["read", "create", "cancel"], + envVars: ["read", "write"], + projectEnvVars: ["read", "write"], + environmentEnvVars: ["read", "write"], + server: ["read", "create", "delete"], + registry: ["read", "create", "delete"], + certificate: ["read", "create", "delete"], + backup: ["read", "create", "delete", "restore"], + volumeBackup: ["read", "create", "update", "delete", "restore"], + schedule: ["read", "create", "update", "delete"], + domain: ["read", "create", "delete"], + destination: ["read", "create", "delete"], + notification: ["read", "create", "delete"], + logs: ["read"], + monitoring: ["read"], + auditLog: ["read"], + }, + }, +]; + const createRoleSchema = z.object({ roleName: z .string() @@ -552,7 +667,7 @@ function HandleCustomRole({ )} - + {isEdit ? "Edit Role" : "Create Custom Role"} @@ -587,6 +702,32 @@ function HandleCustomRole({ /> + {!isEdit && ( +
+

+ + Start from a preset +

+
+ {ROLE_PRESETS.map((preset) => ( + + ))} +
+
+ )} void; }) { return ( -
+

Permissions

{resources.map(([resource, actions]) => {