Merge branch 'main' into ENG-2929

This commit is contained in:
x032205
2025-06-17 15:57:31 -04:00
20 changed files with 146 additions and 25 deletions
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.Identity, "hasDeleteProtection");
if (!hasCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.boolean("hasDeleteProtection").notNullable().defaultTo(false);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.Identity, "hasDeleteProtection");
if (hasCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.dropColumn("hasDeleteProtection");
});
}
}
+2 -1
View File
@@ -12,7 +12,8 @@ export const IdentitiesSchema = z.object({
name: z.string(), name: z.string(),
authMethod: z.string().nullable().optional(), authMethod: z.string().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
hasDeleteProtection: z.boolean().default(false)
}); });
export type TIdentities = z.infer<typeof IdentitiesSchema>; export type TIdentities = z.infer<typeof IdentitiesSchema>;
+3 -1
View File
@@ -48,7 +48,9 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
id: z.string().trim().describe(GROUPS.GET_BY_ID.id) id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
}), }),
response: { response: {
200: GroupsSchema 200: GroupsSchema.extend({
customRoleSlug: z.string().nullable()
})
} }
}, },
handler: async (req) => { handler: async (req) => {
@@ -780,6 +780,7 @@ interface CreateIdentityEvent {
metadata: { metadata: {
identityId: string; identityId: string;
name: string; name: string;
hasDeleteProtection: boolean;
}; };
} }
@@ -788,6 +789,7 @@ interface UpdateIdentityEvent {
metadata: { metadata: {
identityId: string; identityId: string;
name?: string; name?: string;
hasDeleteProtection?: boolean;
}; };
} }
+19 -1
View File
@@ -169,11 +169,29 @@ export const groupDALFactory = (db: TDbClient) => {
} }
}; };
const findById = async (id: string, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.Groups)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.where(`${TableName.Groups}.id`, id)
.select(
selectAllTableCols(TableName.Groups),
db.ref("slug").as("customRoleSlug").withSchema(TableName.OrgRoles)
)
.first();
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });
}
};
return { return {
...groupOrm,
findGroups, findGroups,
findByOrgId, findByOrgId,
findAllGroupPossibleMembers, findAllGroupPossibleMembers,
findGroupsByProjectId, findGroupsByProjectId,
...groupOrm findById
}; };
}; };
+4 -2
View File
@@ -111,12 +111,14 @@ export const IDENTITIES = {
CREATE: { CREATE: {
name: "The name of the identity to create.", name: "The name of the identity to create.",
organizationId: "The organization ID to which the identity belongs.", organizationId: "The organization ID to which the identity belongs.",
role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'." role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'.",
hasDeleteProtection: "Prevents deletion of the identity when enabled."
}, },
UPDATE: { UPDATE: {
identityId: "The ID of the identity to update.", identityId: "The ID of the identity to update.",
name: "The new name of the identity.", name: "The new name of the identity.",
role: "The new role of the identity." role: "The new role of the identity.",
hasDeleteProtection: "Prevents deletion of the identity when enabled."
}, },
DELETE: { DELETE: {
identityId: "The ID of the identity to delete." identityId: "The ID of the identity to delete."
+2 -2
View File
@@ -262,8 +262,8 @@ const envSchema = z
DATADOG_HOSTNAME: zpStr(z.string().optional()), DATADOG_HOSTNAME: zpStr(z.string().optional()),
// PIT // PIT
PIT_CHECKPOINT_WINDOW: zpStr(z.string().optional().default("2")), PIT_CHECKPOINT_WINDOW: zpStr(z.string().optional().default("100")),
PIT_TREE_CHECKPOINT_WINDOW: zpStr(z.string().optional().default("30")), PIT_TREE_CHECKPOINT_WINDOW: zpStr(z.string().optional().default("200")),
/* CORS ----------------------------------------------------------------------------- */ /* CORS ----------------------------------------------------------------------------- */
CORS_ALLOWED_ORIGINS: zpStr( CORS_ALLOWED_ORIGINS: zpStr(
@@ -44,6 +44,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
name: z.string().trim().describe(IDENTITIES.CREATE.name), name: z.string().trim().describe(IDENTITIES.CREATE.name),
organizationId: z.string().trim().describe(IDENTITIES.CREATE.organizationId), organizationId: z.string().trim().describe(IDENTITIES.CREATE.organizationId),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(IDENTITIES.CREATE.role), role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(IDENTITIES.CREATE.role),
hasDeleteProtection: z.boolean().default(false).describe(IDENTITIES.CREATE.hasDeleteProtection),
metadata: z metadata: z
.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) }) .object({ key: z.string().trim().min(1), value: z.string().trim().min(1) })
.array() .array()
@@ -75,6 +76,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
type: EventType.CREATE_IDENTITY, type: EventType.CREATE_IDENTITY,
metadata: { metadata: {
name: identity.name, name: identity.name,
hasDeleteProtection: identity.hasDeleteProtection,
identityId: identity.id identityId: identity.id
} }
} }
@@ -86,6 +88,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
properties: { properties: {
orgId: req.body.organizationId, orgId: req.body.organizationId,
name: identity.name, name: identity.name,
hasDeleteProtection: identity.hasDeleteProtection,
identityId: identity.id, identityId: identity.id,
...req.auditLogInfo ...req.auditLogInfo
} }
@@ -117,6 +120,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
body: z.object({ body: z.object({
name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name), name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name),
role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role), role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role),
hasDeleteProtection: z.boolean().optional().describe(IDENTITIES.UPDATE.hasDeleteProtection),
metadata: z metadata: z
.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) }) .object({ key: z.string().trim().min(1), value: z.string().trim().min(1) })
.array() .array()
@@ -148,6 +152,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
type: EventType.UPDATE_IDENTITY, type: EventType.UPDATE_IDENTITY,
metadata: { metadata: {
name: identity.name, name: identity.name,
hasDeleteProtection: identity.hasDeleteProtection,
identityId: identity.id identityId: identity.id
} }
} }
@@ -243,7 +248,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}) })
}) })
@@ -292,7 +297,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}) })
}).array(), }).array(),
@@ -386,7 +391,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}) })
}).array(), }).array(),
@@ -451,7 +456,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
temporaryAccessEndTime: z.date().nullable().optional() temporaryAccessEndTime: z.date().nullable().optional()
}) })
), ),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}), }),
project: SanitizedProjectSchema.pick({ name: true, id: true, type: true }) project: SanitizedProjectSchema.pick({ name: true, id: true, type: true })
@@ -101,6 +101,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
db.ref("id").as("identityId").withSchema(TableName.Identity), db.ref("id").as("identityId").withSchema(TableName.Identity),
db.ref("name").as("identityName").withSchema(TableName.Identity), db.ref("name").as("identityName").withSchema(TableName.Identity),
db.ref("hasDeleteProtection").withSchema(TableName.Identity),
db.ref("id").withSchema(TableName.IdentityProjectMembership), db.ref("id").withSchema(TableName.IdentityProjectMembership),
db.ref("role").withSchema(TableName.IdentityProjectMembershipRole), db.ref("role").withSchema(TableName.IdentityProjectMembershipRole),
db.ref("id").withSchema(TableName.IdentityProjectMembershipRole).as("membershipRoleId"), db.ref("id").withSchema(TableName.IdentityProjectMembershipRole).as("membershipRoleId"),
@@ -130,6 +131,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
data: docs, data: docs,
parentMapper: ({ parentMapper: ({
identityName, identityName,
hasDeleteProtection,
uaId, uaId,
awsId, awsId,
gcpId, gcpId,
@@ -151,6 +153,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
identity: { identity: {
id: identityId, id: identityId,
name: identityName, name: identityName,
hasDeleteProtection,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
awsId, awsId,
@@ -114,16 +114,18 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth), db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth), db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth), db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth),
db.ref("name").withSchema(TableName.Identity) db.ref("name").withSchema(TableName.Identity),
db.ref("hasDeleteProtection").withSchema(TableName.Identity)
); );
if (data) { if (data) {
const { name } = data; const { name, hasDeleteProtection } = data;
return { return {
...data, ...data,
identity: { identity: {
id: data.identityId, id: data.identityId,
name, name,
hasDeleteProtection,
authMethods: buildAuthMethods(data) authMethods: buildAuthMethods(data)
} }
}; };
@@ -155,7 +157,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection) .orderBy(`${TableName.Identity}.${orderBy}`, orderDirection)
.select( .select(
selectAllTableCols(TableName.IdentityOrgMembership), selectAllTableCols(TableName.IdentityOrgMembership),
db.ref("name").withSchema(TableName.Identity).as("identityName") db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity)
) )
.where(filter) .where(filter)
.as("paginatedIdentity"); .as("paginatedIdentity");
@@ -245,6 +248,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("updatedAt").withSchema("paginatedIdentity"), db.ref("updatedAt").withSchema("paginatedIdentity"),
db.ref("identityId").withSchema("paginatedIdentity").as("identityId"), db.ref("identityId").withSchema("paginatedIdentity").as("identityId"),
db.ref("identityName").withSchema("paginatedIdentity"), db.ref("identityName").withSchema("paginatedIdentity"),
db.ref("hasDeleteProtection").withSchema("paginatedIdentity"),
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth), db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth), db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
@@ -286,6 +290,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
crName, crName,
identityId, identityId,
identityName, identityName,
hasDeleteProtection,
role, role,
roleId, roleId,
id, id,
@@ -324,6 +329,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
identity: { identity: {
id: identityId, id: identityId,
name: identityName, name: identityName,
hasDeleteProtection,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
alicloudId, alicloudId,
@@ -476,6 +482,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("updatedAt").withSchema(TableName.IdentityOrgMembership), db.ref("updatedAt").withSchema(TableName.IdentityOrgMembership),
db.ref("identityId").withSchema(TableName.IdentityOrgMembership).as("identityId"), db.ref("identityId").withSchema(TableName.IdentityOrgMembership).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity),
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth), db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth), db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
@@ -518,6 +525,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
crName, crName,
identityId, identityId,
identityName, identityName,
hasDeleteProtection,
role, role,
roleId, roleId,
total_count, total_count,
@@ -556,6 +564,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
identity: { identity: {
id: identityId, id: identityId,
name: identityName, name: identityName,
hasDeleteProtection,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
alicloudId, alicloudId,
@@ -47,6 +47,7 @@ export const identityServiceFactory = ({
const createIdentity = async ({ const createIdentity = async ({
name, name,
role, role,
hasDeleteProtection,
actor, actor,
orgId, orgId,
actorId, actorId,
@@ -96,7 +97,7 @@ export const identityServiceFactory = ({
} }
const identity = await identityDAL.transaction(async (tx) => { const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name }, tx); const newIdentity = await identityDAL.create({ name, hasDeleteProtection }, tx);
await identityOrgMembershipDAL.create( await identityOrgMembershipDAL.create(
{ {
identityId: newIdentity.id, identityId: newIdentity.id,
@@ -138,6 +139,7 @@ export const identityServiceFactory = ({
const updateIdentity = async ({ const updateIdentity = async ({
id, id,
role, role,
hasDeleteProtection,
name, name,
actor, actor,
actorId, actorId,
@@ -189,7 +191,11 @@ export const identityServiceFactory = ({
} }
const identity = await identityDAL.transaction(async (tx) => { const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = name ? await identityDAL.updateById(id, { name }, tx) : await identityDAL.findById(id, tx); const newIdentity =
name || hasDeleteProtection
? await identityDAL.updateById(id, { name, hasDeleteProtection }, tx)
: await identityDAL.findById(id, tx);
if (role) { if (role) {
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityOrgMembership.id, identityOrgMembership.id,
@@ -272,6 +278,9 @@ export const identityServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
if (identityOrgMembership.identity.hasDeleteProtection)
throw new BadRequestError({ message: "Identity has delete protection" });
const deletedIdentity = await identityDAL.deleteById(id); const deletedIdentity = await identityDAL.deleteById(id);
await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.orgId); await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.orgId);
@@ -5,12 +5,14 @@ import { OrderByDirection, TOrgPermission } from "@app/lib/types";
export type TCreateIdentityDTO = { export type TCreateIdentityDTO = {
role: string; role: string;
name: string; name: string;
hasDeleteProtection: boolean;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
} & TOrgPermission; } & TOrgPermission;
export type TUpdateIdentityDTO = { export type TUpdateIdentityDTO = {
id: string; id: string;
role?: string; role?: string;
hasDeleteProtection?: boolean;
name?: string; name?: string;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
isActorSuperAdmin?: boolean; isActorSuperAdmin?: boolean;
@@ -81,6 +81,7 @@ export type TMachineIdentityCreatedEvent = {
event: PostHogEventTypes.MachineIdentityCreated; event: PostHogEventTypes.MachineIdentityCreated;
properties: { properties: {
name: string; name: string;
hasDeleteProtection: boolean;
orgId: string; orgId: string;
identityId: string; identityId: string;
}; };
@@ -242,6 +242,7 @@ interface CreateIdentityEvent {
metadata: { metadata: {
identityId: string; identityId: string;
name: string; name: string;
hasDeleteProtection: boolean;
}; };
} }
@@ -250,6 +251,7 @@ interface UpdateIdentityEvent {
metadata: { metadata: {
identityId: string; identityId: string;
name?: string; name?: string;
hasDeleteProtection?: boolean;
}; };
} }
@@ -85,12 +85,13 @@ export const useCreateIdentity = () => {
export const useUpdateIdentity = () => { export const useUpdateIdentity = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<Identity, object, UpdateIdentityDTO>({ return useMutation<Identity, object, UpdateIdentityDTO>({
mutationFn: async ({ identityId, name, role, metadata }) => { mutationFn: async ({ identityId, name, role, hasDeleteProtection, metadata }) => {
const { const {
data: { identity } data: { identity }
} = await apiRequest.patch(`/api/v1/identities/${identityId}`, { } = await apiRequest.patch(`/api/v1/identities/${identityId}`, {
name, name,
role, role,
hasDeleteProtection,
metadata metadata
}); });
@@ -14,6 +14,7 @@ export type IdentityTrustedIp = {
export type Identity = { export type Identity = {
id: string; id: string;
name: string; name: string;
hasDeleteProtection: boolean;
authMethods: IdentityAuthMethod[]; authMethods: IdentityAuthMethod[];
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
@@ -83,6 +84,7 @@ export type CreateIdentityDTO = {
name: string; name: string;
organizationId: string; organizationId: string;
role?: string; role?: string;
hasDeleteProtection: boolean;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
}; };
@@ -90,6 +92,7 @@ export type UpdateIdentityDTO = {
identityId: string; identityId: string;
name?: string; name?: string;
role?: string; role?: string;
hasDeleteProtection?: boolean;
organizationId: string; organizationId: string;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
}; };
@@ -15,7 +15,8 @@ import {
IconButton, IconButton,
Input, Input,
Modal, Modal,
ModalContent ModalContent,
Switch
} from "@app/components/v2"; } from "@app/components/v2";
import { useOrganization } from "@app/context"; import { useOrganization } from "@app/context";
import { findOrgMembershipRole } from "@app/helpers/roles"; import { findOrgMembershipRole } from "@app/helpers/roles";
@@ -27,6 +28,7 @@ const schema = z
.object({ .object({
name: z.string().min(1, "Required"), name: z.string().min(1, "Required"),
role: z.object({ slug: z.string(), name: z.string() }), role: z.object({ slug: z.string(), name: z.string() }),
hasDeleteProtection: z.boolean(),
metadata: z metadata: z
.object({ .object({
key: z.string().trim().min(1), key: z.string().trim().min(1),
@@ -64,7 +66,8 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
} = useForm<FormData>({ } = useForm<FormData>({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
name: "" name: "",
hasDeleteProtection: false
} }
}); });
@@ -78,6 +81,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
identityId: string; identityId: string;
name: string; name: string;
role: string; role: string;
hasDeleteProtection: boolean;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
customRole: { customRole: {
name: string; name: string;
@@ -91,22 +95,25 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
reset({ reset({
name: identity.name, name: identity.name,
role: identity.customRole ?? findOrgMembershipRole(roles, identity.role), role: identity.customRole ?? findOrgMembershipRole(roles, identity.role),
hasDeleteProtection: identity.hasDeleteProtection,
metadata: identity.metadata metadata: identity.metadata
}); });
} else { } else {
reset({ reset({
name: "", name: "",
role: findOrgMembershipRole(roles, currentOrg!.defaultMembershipRole) role: findOrgMembershipRole(roles, currentOrg!.defaultMembershipRole),
hasDeleteProtection: false
}); });
} }
}, [popUp?.identity?.data, roles]); }, [popUp?.identity?.data, roles]);
const onFormSubmit = async ({ name, role, metadata }: FormData) => { const onFormSubmit = async ({ name, role, metadata, hasDeleteProtection }: FormData) => {
try { try {
const identity = popUp?.identity?.data as { const identity = popUp?.identity?.data as {
identityId: string; identityId: string;
name: string; name: string;
role: string; role: string;
hasDeleteProtection: boolean;
}; };
if (identity) { if (identity) {
@@ -116,6 +123,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
identityId: identity.identityId, identityId: identity.identityId,
name, name,
role: role.slug || undefined, role: role.slug || undefined,
hasDeleteProtection,
organizationId: orgId, organizationId: orgId,
metadata metadata
}); });
@@ -127,6 +135,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
const { id: createdId } = await createMutateAsync({ const { id: createdId } = await createMutateAsync({
name, name,
role: role.slug || undefined, role: role.slug || undefined,
hasDeleteProtection,
organizationId: orgId, organizationId: orgId,
metadata metadata
}); });
@@ -215,6 +224,24 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="hasDeleteProtection"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<Switch
className="ml-0 mr-2 bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
containerClassName="flex-row-reverse w-fit"
id="delete-protection-enabled"
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={value}
>
<p>Delete Protection {value ? "Enabled" : "Disabled"}</p>
</Switch>
</FormControl>
)}
/>
<div> <div>
<FormLabel label="Metadata" /> <FormLabel label="Metadata" />
</div> </div>
@@ -329,7 +329,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
<FontAwesomeIcon size="sm" icon={faEllipsis} /> <FontAwesomeIcon size="sm" icon={faEllipsis} />
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="mt-3 p-1">
<OrgPermissionCan <OrgPermissionCan
I={OrgPermissionIdentityActions.Edit} I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity} a={OrgPermissionSubjects.Identity}
@@ -47,13 +47,19 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
size="xs" size="xs"
rightIcon={<FontAwesomeIcon className="ml-1" icon={faChevronDown} />} rightIcon={
<FontAwesomeIcon
className="ml-1 transition-transform duration-200 group-data-[state=open]:rotate-180"
icon={faChevronDown}
/>
}
colorSchema="secondary" colorSchema="secondary"
className="group select-none"
> >
Options Options
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[120px]" align="end"> <DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
<OrgPermissionCan <OrgPermissionCan
I={OrgPermissionIdentityActions.Edit} I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity} a={OrgPermissionSubjects.Identity}
@@ -68,6 +74,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
handlePopUpOpen("identity", { handlePopUpOpen("identity", {
identityId, identityId,
name: data.identity.name, name: data.identity.name,
hasDeleteProtection: data.identity.hasDeleteProtection,
role: data.role, role: data.role,
customRole: data.customRole customRole: data.customRole
}); });
@@ -131,6 +138,12 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
<p className="text-sm font-semibold text-mineshaft-300">Name</p> <p className="text-sm font-semibold text-mineshaft-300">Name</p>
<p className="text-sm text-mineshaft-300">{data.identity.name}</p> <p className="text-sm text-mineshaft-300">{data.identity.name}</p>
</div> </div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Delete Protection</p>
<p className="text-sm text-mineshaft-300">
{data.identity.hasDeleteProtection ? "On" : "Off"}
</p>
</div>
<div className="mb-4"> <div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Organization Role</p> <p className="text-sm font-semibold text-mineshaft-300">Organization Role</p>
<p className="text-sm text-mineshaft-300">{data.role}</p> <p className="text-sm text-mineshaft-300">{data.role}</p>
@@ -35,7 +35,7 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
Options Options
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[120px]" align="end"> <DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
<OrgPermissionCan <OrgPermissionCan
I={OrgPermissionIdentityActions.Edit} I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity} a={OrgPermissionSubjects.Identity}