mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-13 07:25:17 +00:00
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.
This commit is contained in:
@@ -1,13 +1,20 @@
|
|||||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
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 { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
|
||||||
import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate";
|
import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -24,11 +31,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -38,6 +40,11 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
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) */
|
/** Resources that should be hidden from the custom role editor (better-auth internals) */
|
||||||
const HIDDEN_RESOURCES = ["organization", "invitation", "team", "ac"];
|
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<string, string[]>;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
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({
|
const createRoleSchema = z.object({
|
||||||
roleName: z
|
roleName: z
|
||||||
.string()
|
.string()
|
||||||
@@ -552,7 +667,7 @@ function HandleCustomRole({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-[85vh] sm:max-w-5xl overflow-y-auto">
|
<DialogContent className="max-h-[85vh] sm:max-w-5xl overflow-y-auto space-y-2">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{isEdit ? "Edit Role" : "Create Custom Role"}
|
{isEdit ? "Edit Role" : "Create Custom Role"}
|
||||||
@@ -587,6 +702,32 @@ function HandleCustomRole({
|
|||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
{!isEdit && (
|
||||||
|
<div className="space-y-2 mt-4">
|
||||||
|
<p className="text-sm font-medium flex items-center gap-1.5">
|
||||||
|
<Sparkles className="size-3.5 text-muted-foreground" />
|
||||||
|
Start from a preset
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||||
|
{ROLE_PRESETS.map((preset) => (
|
||||||
|
<button
|
||||||
|
key={preset.name}
|
||||||
|
type="button"
|
||||||
|
className="rounded-lg border p-3 text-left hover:bg-muted/50 transition-colors cursor-pointer space-y-1"
|
||||||
|
onClick={() => {
|
||||||
|
form.setValue("roleName", preset.name);
|
||||||
|
setPermissions({ ...preset.permissions });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="text-sm font-medium">{preset.label}</p>
|
||||||
|
<p className="text-xs text-muted-foreground leading-snug">
|
||||||
|
{preset.description}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<PermissionEditor
|
<PermissionEditor
|
||||||
resources={visibleResources}
|
resources={visibleResources}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
@@ -843,7 +984,7 @@ function PermissionEditor({
|
|||||||
onToggle: (resource: string, action: string) => void;
|
onToggle: (resource: string, action: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3 mt-4">
|
||||||
<p className="text-sm font-medium">Permissions</p>
|
<p className="text-sm font-medium">Permissions</p>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
{resources.map(([resource, actions]) => {
|
{resources.map(([resource, actions]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user