Skip to content

Commit 9067452

Browse files
committed
feat: add role presets for custom role management
- Introduced predefined role presets with default permissions for viewer, developer, deployer, and devops roles to streamline custom role creation. - Enhanced the UI to allow users to start from a preset role, improving the user experience in managing custom roles. - Updated imports and adjusted component formatting for better readability.
1 parent 1fa4d5b commit 9067452

File tree

1 file changed

+151
-10
lines changed

1 file changed

+151
-10
lines changed

apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx

Lines changed: 151 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
2-
import { Loader2, PlusIcon, ShieldCheck, TrashIcon, Users } from "lucide-react";
2+
import {
3+
Loader2,
4+
PlusIcon,
5+
ShieldCheck,
6+
Sparkles,
7+
TrashIcon,
8+
Users,
9+
} from "lucide-react";
310
import { useEffect, useState } from "react";
411
import { useForm } from "react-hook-form";
512
import { toast } from "sonner";
613
import { z } from "zod";
7-
import { DialogAction } from "@/components/shared/dialog-action";
814
import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate";
9-
import { Button } from "@/components/ui/button";
1015
import { AlertBlock } from "@/components/shared/alert-block";
16+
import { DialogAction } from "@/components/shared/dialog-action";
17+
import { Button } from "@/components/ui/button";
1118
import {
1219
Card,
1320
CardContent,
@@ -24,11 +31,6 @@ import {
2431
DialogTitle,
2532
DialogTrigger,
2633
} from "@/components/ui/dialog";
27-
import {
28-
Popover,
29-
PopoverContent,
30-
PopoverTrigger,
31-
} from "@/components/ui/popover";
3234
import {
3335
Form,
3436
FormControl,
@@ -38,6 +40,11 @@ import {
3840
FormMessage,
3941
} from "@/components/ui/form";
4042
import { Input } from "@/components/ui/input";
43+
import {
44+
Popover,
45+
PopoverContent,
46+
PopoverTrigger,
47+
} from "@/components/ui/popover";
4148
import { Switch } from "@/components/ui/switch";
4249
import { api } from "@/utils/api";
4350

@@ -407,6 +414,114 @@ const ACTION_META: Record<
407414
/** Resources that should be hidden from the custom role editor (better-auth internals) */
408415
const HIDDEN_RESOURCES = ["organization", "invitation", "team", "ac"];
409416

417+
/** Predefined role presets with sensible permission defaults */
418+
const ROLE_PRESETS: {
419+
name: string;
420+
label: string;
421+
description: string;
422+
permissions: Record<string, string[]>;
423+
}[] = [
424+
{
425+
name: "viewer",
426+
label: "Viewer",
427+
description: "Read-only access across all resources",
428+
permissions: {
429+
service: ["read"],
430+
environment: ["read"],
431+
docker: ["read"],
432+
sshKeys: ["read"],
433+
gitProviders: ["read"],
434+
traefikFiles: ["read"],
435+
api: ["read"],
436+
volume: ["read"],
437+
deployment: ["read"],
438+
envVars: ["read"],
439+
projectEnvVars: ["read"],
440+
environmentEnvVars: ["read"],
441+
server: ["read"],
442+
registry: ["read"],
443+
certificate: ["read"],
444+
backup: ["read"],
445+
volumeBackup: ["read"],
446+
schedule: ["read"],
447+
domain: ["read"],
448+
destination: ["read"],
449+
notification: ["read"],
450+
member: ["read"],
451+
logs: ["read"],
452+
monitoring: ["read"],
453+
auditLog: ["read"],
454+
},
455+
},
456+
{
457+
name: "developer",
458+
label: "Developer",
459+
description: "Deploy services, manage env vars, domains, and view logs",
460+
permissions: {
461+
project: ["create"],
462+
service: ["create", "read"],
463+
environment: ["create", "read"],
464+
docker: ["read"],
465+
gitProviders: ["read"],
466+
api: ["read"],
467+
volume: ["read", "create", "delete"],
468+
deployment: ["read", "create", "cancel"],
469+
envVars: ["read", "write"],
470+
projectEnvVars: ["read"],
471+
environmentEnvVars: ["read"],
472+
domain: ["read", "create", "delete"],
473+
schedule: ["read", "create", "update", "delete"],
474+
logs: ["read"],
475+
monitoring: ["read"],
476+
},
477+
},
478+
{
479+
name: "deployer",
480+
label: "Deployer",
481+
description: "Trigger and manage deployments only",
482+
permissions: {
483+
service: ["read"],
484+
environment: ["read"],
485+
deployment: ["read", "create", "cancel"],
486+
logs: ["read"],
487+
monitoring: ["read"],
488+
},
489+
},
490+
{
491+
name: "devops",
492+
label: "DevOps",
493+
description:
494+
"Full infrastructure access: servers, registries, certs, backups, and deployments",
495+
permissions: {
496+
project: ["create", "delete"],
497+
service: ["create", "read", "delete"],
498+
environment: ["create", "read", "delete"],
499+
docker: ["read"],
500+
sshKeys: ["read", "create", "delete"],
501+
gitProviders: ["read", "create", "delete"],
502+
traefikFiles: ["read", "write"],
503+
api: ["read"],
504+
volume: ["read", "create", "delete"],
505+
deployment: ["read", "create", "cancel"],
506+
envVars: ["read", "write"],
507+
projectEnvVars: ["read", "write"],
508+
environmentEnvVars: ["read", "write"],
509+
server: ["read", "create", "delete"],
510+
registry: ["read", "create", "delete"],
511+
certificate: ["read", "create", "delete"],
512+
backup: ["read", "create", "delete", "restore"],
513+
volumeBackup: ["read", "create", "update", "delete", "restore"],
514+
schedule: ["read", "create", "update", "delete"],
515+
domain: ["read", "create", "delete"],
516+
destination: ["read", "create", "delete"],
517+
notification: ["read", "create", "delete"],
518+
logs: ["read"],
519+
monitoring: ["read"],
520+
auditLog: ["read"],
521+
},
522+
},
523+
];
524+
410525
const createRoleSchema = z.object({
411526
roleName: z
412527
.string()
@@ -552,7 +667,7 @@ function HandleCustomRole({
552667
</Button>
553668
)}
554669
</DialogTrigger>
555-
<DialogContent className="max-h-[85vh] sm:max-w-5xl overflow-y-auto">
670+
<DialogContent className="max-h-[85vh] sm:max-w-5xl overflow-y-auto space-y-2">
556671
<DialogHeader>
557672
<DialogTitle>
558673
{isEdit ? "Edit Role" : "Create Custom Role"}
@@ -587,6 +702,32 @@ function HandleCustomRole({
587702
/>
588703
</form>
589704
</Form>
705+
{!isEdit && (
706+
<div className="space-y-2 mt-4">
707+
<p className="text-sm font-medium flex items-center gap-1.5">
708+
<Sparkles className="size-3.5 text-muted-foreground" />
709+
Start from a preset
710+
</p>
711+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
712+
{ROLE_PRESETS.map((preset) => (
713+
<button
714+
key={preset.name}
715+
type="button"
716+
className="rounded-lg border p-3 text-left hover:bg-muted/50 transition-colors cursor-pointer space-y-1"
717+
onClick={() => {
718+
form.setValue("roleName", preset.name);
719+
setPermissions({ ...preset.permissions });
720+
}}
721+
>
722+
<p className="text-sm font-medium">{preset.label}</p>
723+
<p className="text-xs text-muted-foreground leading-snug">
724+
{preset.description}
725+
</p>
726+
</button>
727+
))}
728+
</div>
729+
</div>
730+
)}
590731
<PermissionEditor
591732
resources={visibleResources}
592733
permissions={permissions}
@@ -843,7 +984,7 @@ function PermissionEditor({
843984
onToggle: (resource: string, action: string) => void;
844985
}) {
845986
return (
846-
<div className="space-y-3">
987+
<div className="space-y-3 mt-4">
847988
<p className="text-sm font-medium">Permissions</p>
848989
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
849990
{resources.map(([resource, actions]) => {

0 commit comments

Comments
 (0)