improvements: address feedback

This commit is contained in:
Scott Wilson
2025-05-07 11:23:51 -07:00
parent 0ded2e51ba
commit 794fc9c2a2
12 changed files with 56 additions and 16 deletions

View File

@@ -13,7 +13,7 @@ export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => (
name: InfisicalProjectTemplate.Default,
createdAt: new Date(),
updatedAt: new Date(),
description: `Infisical's ${type ? `"${type}"` : ""} default project template`,
description: `Infisical's ${type} default project template`,
environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
({ name, slug, permissions }) => ({

View File

@@ -160,6 +160,17 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
if (environments && type !== ProjectType.SecretManager) {
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
}
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `Failed to create project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
});
}
const isConflictingName = Boolean(
await projectTemplateDAL.findOne({
name: params.name,
@@ -216,6 +227,13 @@ export const projectTemplateServiceFactory = ({
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `Failed to update project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
});
}
if (params.name && projectTemplate.name !== params.name) {
const isConflictingName = Boolean(
await projectTemplateDAL.findOne({

View File

@@ -1,3 +1,5 @@
import { v4 as uuidv4 } from "uuid";
import { ProjectMembershipRole, ProjectType } from "@app/db/schemas";
import {
cryptographicOperatorPermissions,
@@ -12,7 +14,7 @@ import { TGetPredefinedRolesDTO } from "@app/services/project-role/project-role-
export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetPredefinedRolesDTO) => {
return [
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // dummy userid
id: uuidv4(),
projectId,
name: "Admin",
slug: ProjectMembershipRole.Admin,
@@ -22,7 +24,7 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP
updatedAt: new Date()
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c70", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "Developer",
slug: ProjectMembershipRole.Member,
@@ -32,7 +34,7 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP
updatedAt: new Date()
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c73", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "SSH Host Bootstrapper",
slug: ProjectMembershipRole.SshHostBootstrapper,
@@ -43,7 +45,7 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP
type: ProjectType.SSH
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c74", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "Cryptographic Operator",
slug: ProjectMembershipRole.KmsCryptographicOperator,
@@ -54,7 +56,7 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP
type: ProjectType.KMS
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c71", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "Viewer",
slug: ProjectMembershipRole.Viewer,
@@ -64,7 +66,7 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP
updatedAt: new Date()
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "No Access",
slug: ProjectMembershipRole.NoAccess,
@@ -73,5 +75,5 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP
createdAt: new Date(),
updatedAt: new Date()
}
].filter(({ slug, type }) => (type ? type === projectType : true) && (!roleFilter || roleFilter.includes(slug)));
].filter(({ slug, type }) => (type ? type === projectType : true) && (!roleFilter || roleFilter === slug));
};

View File

@@ -117,11 +117,14 @@ export const projectRoleServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) {
const predefinedRole = getPredefinedRoles({
const [predefinedRole] = getPredefinedRoles({
projectId: project.id,
projectType: project.type as ProjectType,
roleFilter: roleSlug as ProjectMembershipRole
})[0];
});
if (!predefinedRole) throw new NotFoundError({ message: `Default role with slug '${roleSlug}' not found` });
return { ...predefinedRole, permissions: UnpackedPermissionSchema.array().parse(predefinedRole.permissions) };
}

View File

@@ -33,7 +33,7 @@ In the following steps, we'll explore how to set up a project template.
<Tab title="Infisical UI">
<Steps>
<Step title="Creating a Project Template">
Navigate to the Project Templates tab on the Feature Settings page for the project type you want to create a template for and tap on the **Add Template** button.
Navigate to the **Project Templates** tab on the Feature Settings page for the project type you want to create a template for and tap on the **Add Template** button.
![project template add button](/images/platform/project-templates/project-template-add-button.png)
Specify your template details. Here's some guidance on each field:

View File

@@ -50,6 +50,10 @@ we will register a remote host with Infisical through a [machine identity](/docu
This role grants the ability to **Create** and **Issue Host Certificates** on the **SSH Host** resource; this will enable the linked machine identity to bootstrap a remote host with Infisical
and establish the necessary configuration on it.
<Tip>
If you plan to use a custom role to bootstrap SSH hosts, ensure the role has the **Create** and **Issue Host Certificates** on the **SSH Host** resource.
</Tip>
![ssh add identity to project](/images/platform/ssh/v2/ssh-add-identity-to-project.png)
</Step>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -19,7 +19,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
import { slugSchema } from "@app/lib/schemas";
@@ -56,6 +56,8 @@ export const ProjectTemplateEnvironmentsForm = ({
resolver: zodResolver(formSchema)
});
const { subscription } = useSubscription();
const {
fields: environments,
move,
@@ -90,6 +92,9 @@ export const ProjectTemplateEnvironmentsForm = ({
}
};
const isEnvironmentLimitExceeded =
Boolean(subscription.environmentLimit) && environments.length >= subscription.environmentLimit;
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
@@ -139,6 +144,8 @@ export const ProjectTemplateEnvironmentsForm = ({
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.ProjectTemplates}
renderTooltip={isEnvironmentLimitExceeded ? true : undefined}
allowedLabel={`Plan environment limit of ${subscription.environmentLimit} exceeded. Contact Infisical to increase limit.`}
>
{(isAllowed) => (
<Button
@@ -148,7 +155,7 @@ export const ProjectTemplateEnvironmentsForm = ({
variant="solid"
size="xs"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
isDisabled={!isAllowed}
isDisabled={!isAllowed || isEnvironmentLimitExceeded}
>
Add Environment
</Button>

View File

@@ -57,7 +57,13 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
});
const onFormSubmit = async (data: FormData) => {
if (!projectType) return;
if (!projectType) {
createNotification({
text: "Failed to determine project type",
type: "error"
});
return;
}
const mutation = projectTemplate
? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data })

View File

@@ -7,7 +7,7 @@ export const CertManagerSettingsPage = () => {
return (
<>
<Helmet>
<title>Cert Managment Settings</title>
<title>Cert Management Settings</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">

View File

@@ -53,7 +53,7 @@ export const NewPermissionRule = ({ onClose }: Props) => {
<Controller
control={form.control}
name="type"
defaultValue={ProjectPermissionSub.Secrets}
defaultValue={ProjectPermissionSub.Project}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Subject" errorText={error?.message} isError={Boolean(error)}>
<Select