mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-20 05:15:23 +02:00
feat: add accessedEnvironments field to user and member schemas, enhancing permission management for environment access
This commit is contained in:
@@ -155,6 +155,7 @@ export const extractServices = (data: Environment | undefined) => {
|
||||
|
||||
const addPermissions = z.object({
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedEnvironments: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional().default(false),
|
||||
canCreateServices: z.boolean().optional().default(false),
|
||||
@@ -200,6 +201,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedEnvironments: data.accessedEnvironments || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
canCreateProjects: data.canCreateProjects,
|
||||
canCreateServices: data.canCreateServices,
|
||||
@@ -223,6 +225,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
canDeleteProjects: data.canDeleteProjects,
|
||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedEnvironments: data.accessedEnvironments || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
@@ -456,91 +459,317 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
No projects found
|
||||
</p>
|
||||
)}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{projects?.map((item, index) => {
|
||||
const applications = item.environments.flatMap((env) =>
|
||||
extractServices(env),
|
||||
);
|
||||
<div className="grid md:grid-cols-1 gap-4">
|
||||
{projects?.map((project, projectIndex) => {
|
||||
return (
|
||||
<FormField
|
||||
key={`project-${index}`}
|
||||
key={`project-${projectIndex}`}
|
||||
control={form.control}
|
||||
name="accessedProjects"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={item.projectId}
|
||||
className="flex flex-col items-start space-x-4 rounded-lg p-4 border"
|
||||
key={project.projectId}
|
||||
className="flex flex-col items-start rounded-lg p-4 border"
|
||||
>
|
||||
<div className="flex flex-row gap-4">
|
||||
{/* Project Header */}
|
||||
<div className="flex flex-row gap-4 items-center w-full">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(
|
||||
item.projectId,
|
||||
project.projectId,
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([
|
||||
...(field.value || []),
|
||||
item.projectId,
|
||||
])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) =>
|
||||
value !== item.projectId,
|
||||
),
|
||||
if (checked) {
|
||||
// Add the project
|
||||
field.onChange([
|
||||
...(field.value || []),
|
||||
project.projectId,
|
||||
]);
|
||||
} else {
|
||||
// Remove the project
|
||||
field.onChange(
|
||||
field.value?.filter(
|
||||
(value) =>
|
||||
value !== project.projectId,
|
||||
),
|
||||
);
|
||||
|
||||
// Also remove all environments and services from this project
|
||||
const currentEnvs =
|
||||
form.getValues(
|
||||
"accessedEnvironments",
|
||||
) || [];
|
||||
const currentServices =
|
||||
form.getValues(
|
||||
"accessedServices",
|
||||
) || [];
|
||||
|
||||
// Get all environment IDs from this project
|
||||
const projectEnvIds =
|
||||
project.environments.map(
|
||||
(env) => env.environmentId,
|
||||
);
|
||||
|
||||
// Get all service IDs from this project
|
||||
const projectServiceIds =
|
||||
project.environments.flatMap(
|
||||
(env) =>
|
||||
extractServices(env).map(
|
||||
(service) => service.id,
|
||||
),
|
||||
);
|
||||
|
||||
// Remove environments and services from this project
|
||||
form.setValue(
|
||||
"accessedEnvironments",
|
||||
currentEnvs.filter(
|
||||
(envId) =>
|
||||
!projectEnvIds.includes(envId),
|
||||
),
|
||||
);
|
||||
form.setValue(
|
||||
"accessedServices",
|
||||
currentServices.filter(
|
||||
(serviceId) =>
|
||||
!projectServiceIds.includes(
|
||||
serviceId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-medium text-primary">
|
||||
{item.name}
|
||||
<FormLabel className="text-base font-semibold text-primary">
|
||||
{project.name}
|
||||
</FormLabel>
|
||||
</div>
|
||||
{applications.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No services found
|
||||
</p>
|
||||
)}
|
||||
{applications?.map((item, index) => (
|
||||
<FormField
|
||||
key={`project-${index}`}
|
||||
control={form.control}
|
||||
name="accessedServices"
|
||||
render={({ field }) => {
|
||||
|
||||
{/* Environments */}
|
||||
<div className="ml-6 w-full space-y-3">
|
||||
{project.environments.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No environments found
|
||||
</p>
|
||||
)}
|
||||
{project.environments.map(
|
||||
(environment, envIndex) => {
|
||||
const services =
|
||||
extractServices(environment);
|
||||
return (
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
<div
|
||||
key={`env-${envIndex}`}
|
||||
className="border-l-2 border-muted pl-4"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(
|
||||
item.id,
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([
|
||||
...(field.value || []),
|
||||
item.id,
|
||||
])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) =>
|
||||
value !== item.id,
|
||||
),
|
||||
{/* Environment Header with Checkbox */}
|
||||
<FormField
|
||||
key={`env-${envIndex}`}
|
||||
control={form.control}
|
||||
name="accessedEnvironments"
|
||||
render={({ field: envField }) => (
|
||||
<FormItem className="flex flex-row items-center space-x-3 space-y-0 mb-2">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={envField.value?.includes(
|
||||
environment.environmentId,
|
||||
)}
|
||||
onCheckedChange={(
|
||||
checked,
|
||||
) => {
|
||||
if (checked) {
|
||||
// Add the environment
|
||||
envField.onChange([
|
||||
...(envField.value ||
|
||||
[]),
|
||||
environment.environmentId,
|
||||
]);
|
||||
|
||||
// Auto-select the project if not already selected
|
||||
const currentProjects =
|
||||
form.getValues(
|
||||
"accessedProjects",
|
||||
) || [];
|
||||
if (
|
||||
!currentProjects.includes(
|
||||
project.projectId,
|
||||
)
|
||||
) {
|
||||
form.setValue(
|
||||
"accessedProjects",
|
||||
[
|
||||
...currentProjects,
|
||||
project.projectId,
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Remove the environment
|
||||
envField.onChange(
|
||||
envField.value?.filter(
|
||||
(value) =>
|
||||
value !==
|
||||
environment.environmentId,
|
||||
),
|
||||
);
|
||||
|
||||
// Also remove all services from this environment
|
||||
const currentServices =
|
||||
form.getValues(
|
||||
"accessedServices",
|
||||
) || [];
|
||||
const environmentServiceIds =
|
||||
services.map(
|
||||
(service) =>
|
||||
service.id,
|
||||
);
|
||||
|
||||
form.setValue(
|
||||
"accessedServices",
|
||||
currentServices.filter(
|
||||
(serviceId) =>
|
||||
!environmentServiceIds.includes(
|
||||
serviceId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full" />
|
||||
<FormLabel className="text-sm font-medium text-foreground cursor-pointer">
|
||||
{environment.name}
|
||||
</FormLabel>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
({services.length} services)
|
||||
</span>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Services */}
|
||||
<div className="ml-4 space-y-2">
|
||||
{services.length === 0 && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
No services found
|
||||
</p>
|
||||
)}
|
||||
{services.map(
|
||||
(service, serviceIndex) => (
|
||||
<FormField
|
||||
key={`service-${serviceIndex}`}
|
||||
control={form.control}
|
||||
name="accessedServices"
|
||||
render={({
|
||||
field: serviceField,
|
||||
}) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={service.id}
|
||||
className="flex flex-row items-center space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={serviceField.value?.includes(
|
||||
service.id,
|
||||
)}
|
||||
onCheckedChange={(
|
||||
checked,
|
||||
) => {
|
||||
if (checked) {
|
||||
// Add the service
|
||||
serviceField.onChange(
|
||||
[
|
||||
...(serviceField.value ||
|
||||
[]),
|
||||
service.id,
|
||||
],
|
||||
);
|
||||
|
||||
// Auto-select the environment if not already selected
|
||||
const currentEnvs =
|
||||
form.getValues(
|
||||
"accessedEnvironments",
|
||||
) || [];
|
||||
if (
|
||||
!currentEnvs.includes(
|
||||
environment.environmentId,
|
||||
)
|
||||
) {
|
||||
form.setValue(
|
||||
"accessedEnvironments",
|
||||
[
|
||||
...currentEnvs,
|
||||
environment.environmentId,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-select the project if not already selected
|
||||
const currentProjects =
|
||||
form.getValues(
|
||||
"accessedProjects",
|
||||
) || [];
|
||||
if (
|
||||
!currentProjects.includes(
|
||||
project.projectId,
|
||||
)
|
||||
) {
|
||||
form.setValue(
|
||||
"accessedProjects",
|
||||
[
|
||||
...currentProjects,
|
||||
project.projectId,
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Remove the service
|
||||
serviceField.onChange(
|
||||
serviceField.value?.filter(
|
||||
(value) =>
|
||||
value !==
|
||||
service.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${
|
||||
service.type ===
|
||||
"application"
|
||||
? "bg-green-500"
|
||||
: service.type ===
|
||||
"compose"
|
||||
? "bg-purple-500"
|
||||
: "bg-orange-500"
|
||||
}`}
|
||||
/>
|
||||
<FormLabel className="text-sm text-muted-foreground cursor-pointer">
|
||||
{service.name}
|
||||
</FormLabel>
|
||||
<span className="text-xs text-muted-foreground/70 capitalize">
|
||||
({service.type})
|
||||
</span>
|
||||
</div>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm text-muted-foreground">
|
||||
{item.name}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
}}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user