feat: completed migration

This commit is contained in:
=
2024-12-11 15:28:46 +05:30
parent 5151c91760
commit 69bf9dc20f
28 changed files with 1512 additions and 1484 deletions

View File

@@ -1,13 +1,248 @@
import { Knex } from "knex";
import { v4 as uuidV4 } from "uuid";
import slugify from "@sindresorhus/slugify";
import { TableName } from "../schemas";
import { ProjectType, TableName } from "../schemas";
import { alphaNumericNanoId } from "@app/lib/nanoid";
/* eslint-disable no-await-in-loop,no-param-reassign,@typescript-eslint/ban-ts-comment */
const newProject = async (knex: Knex, projectId: string, projectType: ProjectType) => {
const newProjectId = uuidV4();
const project = await knex(TableName.Project).where("id", projectId).first();
await knex(TableName.Project).insert({
...project,
type: projectType,
// @ts-ignore id is required
id: newProjectId,
slug: slugify(`${project?.name}-${alphaNumericNanoId(4)}`)
});
const customRoleMapping: Record<string, string> = {};
const projectCustomRoles = await knex(TableName.ProjectRoles).where("projectId", projectId);
if (projectCustomRoles.length) {
await knex(TableName.ProjectRoles).insert(
projectCustomRoles.map((el) => {
const id = uuidV4();
customRoleMapping[el.id] = id;
el.id = id;
el.projectId = newProjectId;
el.permissions = el.permissions ? JSON.stringify(el.permissions) : el.permissions;
return el;
})
);
}
const groupMembershipMapping: Record<string, string> = {};
const groupMemberships = await knex(TableName.GroupProjectMembership).where("projectId", projectId);
if (groupMemberships.length) {
await knex(TableName.GroupProjectMembership).insert(
groupMemberships.map((el) => {
const id = uuidV4();
groupMembershipMapping[el.id] = id;
el.id = id;
el.projectId = newProjectId;
return el;
})
);
}
const groupMembershipRoles = await knex(TableName.GroupProjectMembershipRole).whereIn(
"projectMembershipId",
groupMemberships.map((el) => el.id)
);
if (groupMembershipRoles.length) {
await knex(TableName.GroupProjectMembershipRole).insert(
groupMembershipRoles.map((el) => {
const id = uuidV4();
el.id = id;
el.projectMembershipId = groupMembershipMapping[el.id];
el.customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return el;
})
);
}
const identityProjectMembershipMapping: Record<string, string> = {};
const identities = await knex(TableName.IdentityProjectMembership).where("projectId", projectId);
if (identities.length) {
await knex(TableName.IdentityProjectMembership).insert(
identities.map((el) => {
const id = uuidV4();
identityProjectMembershipMapping[el.id] = id;
el.id = id;
el.projectId = newProjectId;
return el;
})
);
}
const identitiesRoles = await knex(TableName.IdentityProjectMembershipRole).whereIn(
"projectMembershipId",
identities.map((el) => el.id)
);
if (identitiesRoles.length) {
await knex(TableName.IdentityProjectMembershipRole).insert(
identitiesRoles.map((el) => {
const id = uuidV4();
el.id = id;
el.projectMembershipId = identityProjectMembershipMapping[el.projectMembershipId];
el.customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return el;
})
);
}
const projectMembershipMapping: Record<string, string> = {};
const projectUserMembers = await knex(TableName.ProjectMembership).where("projectId", projectId);
if (projectUserMembers.length) {
await knex(TableName.ProjectMembership).insert(
projectUserMembers.map((el) => {
const id = uuidV4();
projectMembershipMapping[el.id] = id;
el.id = id;
el.projectId = newProjectId;
return el;
})
);
}
const membershipRoles = await knex(TableName.ProjectUserMembershipRole).whereIn(
"projectMembershipId",
projectUserMembers.map((el) => el.id)
);
if (membershipRoles.length) {
await knex(TableName.ProjectUserMembershipRole).insert(
membershipRoles.map((el) => {
const id = uuidV4();
el.id = id;
el.projectMembershipId = projectMembershipMapping[el.projectMembershipId];
el.customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return el;
})
);
}
const kmsKeys = await knex(TableName.KmsKey).where("projectId", projectId).andWhere("isReserved", true);
if (kmsKeys.length) {
await knex(TableName.KmsKey).insert(
kmsKeys.map((el) => {
const id = uuidV4();
el.id = id;
el.projectId = newProjectId;
el.slug = slugify(alphaNumericNanoId(8).toLowerCase());
return el;
})
);
}
const projectBot = await knex(TableName.ProjectBot).where("projectId", projectId).first();
if (projectBot) {
const newProjectBot = { ...projectBot, id: uuidV4(), projectId: newProjectId };
await knex(TableName.ProjectBot).insert(newProjectBot);
}
const projectKeys = await knex(TableName.ProjectKeys).where("projectId", projectId);
if (projectKeys.length) {
await knex(TableName.ProjectKeys).insert(
projectKeys.map((el) => {
const id = uuidV4();
el.id = id;
el.projectId = newProjectId;
return el;
})
);
}
const serviceTokens = await knex(TableName.ServiceToken).where("projectId", projectId);
if (serviceTokens.length) {
await knex(TableName.ServiceToken).insert(
serviceTokens.map((el) => {
el.id = uuidV4();
el.projectId = projectId;
el.scopes = el.scopes ? JSON.stringify(el.scopes) : el.scopes;
return el;
})
);
}
return newProjectId;
};
/* eslint-enable */
const BATCH_SIZE = 500;
export async function up(knex: Knex): Promise<void> {
const hasSplitMappingTable = await knex.schema.hasTable(TableName.ProjectSplitBackfillIds);
if (!hasSplitMappingTable) {
await knex.schema.createTable(TableName.ProjectSplitBackfillIds, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("sourceProjectId", 36).notNullable();
t.foreign("sourceProjectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.string("destinationProjectType").notNullable();
t.string("destinationProjectId", 36).notNullable();
t.foreign("destinationProjectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
if (!hasTypeColumn) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.string("type");
});
let projectsToBeTyped;
do {
// eslint-disable-next-line no-await-in-loop
projectsToBeTyped = await knex(TableName.Project).whereNull("type").limit(BATCH_SIZE).select("id");
if (projectsToBeTyped.length) {
// eslint-disable-next-line no-await-in-loop
await knex(TableName.Project)
.whereIn(
"id",
projectsToBeTyped.map((el) => el.id)
)
.update({ type: ProjectType.SecretManager });
}
} while (projectsToBeTyped.length > 0);
const projectsWithCertificates = await knex(TableName.CertificateAuthority)
.distinct("projectId")
.select("projectId");
/* eslint-disable no-await-in-loop,no-param-reassign */
for (const { projectId } of projectsWithCertificates) {
const newProjectId = await newProject(knex, projectId, ProjectType.CertificateManager);
await knex(TableName.CertificateAuthority).where("projectId", projectId).update({ projectId: newProjectId });
await knex(TableName.PkiAlert).where("projectId", projectId).update({ projectId: newProjectId });
await knex(TableName.PkiCollection).where("projectId", projectId).update({ projectId: newProjectId });
await knex(TableName.ProjectSplitBackfillIds).insert({
sourceProjectId: projectId,
destinationProjectType: ProjectType.CertificateManager,
destinationProjectId: newProjectId
});
}
const projectsWithCmek = await knex(TableName.KmsKey)
.where("isReserved", false)
.whereNotNull("projectId")
.distinct("projectId")
.select("projectId");
for (const { projectId } of projectsWithCmek) {
if (projectId) {
const newProjectId = await newProject(knex, projectId, ProjectType.Cmek);
await knex(TableName.KmsKey)
.where({
isReserved: false,
projectId
})
.update({ projectId: newProjectId });
await knex(TableName.ProjectSplitBackfillIds).insert({
sourceProjectId: projectId,
destinationProjectType: ProjectType.Cmek,
destinationProjectId: newProjectId
});
}
}
/* eslint-enable */
await knex.schema.alterTable(TableName.Project, (t) => {
t.string("type").notNullable().alter();
});
}
}
@@ -18,4 +253,9 @@ export async function down(knex: Knex): Promise<void> {
t.dropColumn("type");
});
}
const hasSplitMappingTable = await knex.schema.hasTable(TableName.ProjectSplitBackfillIds);
if (hasSplitMappingTable) {
await knex.schema.dropTableIfExists(TableName.ProjectSplitBackfillIds);
}
}

View File

@@ -106,6 +106,7 @@ export enum TableName {
SecretApprovalRequestSecretV2 = "secret_approval_requests_secrets_v2",
SecretApprovalRequestSecretTagV2 = "secret_approval_request_secret_tags_v2",
SnapshotSecretV2 = "secret_snapshot_secrets_v2",
ProjectSplitBackfillIds = "project_split_backfill_ids",
// junction tables with tags
SecretV2JnTag = "secret_v2_tag_junction",
JnSecretTag = "secret_tag_junction",

View File

@@ -428,7 +428,8 @@ export const ORGANIZATIONS = {
search: "The text string that identity membership names will be filtered by."
},
GET_PROJECTS: {
organizationId: "The ID of the organization to get projects from."
organizationId: "The ID of the organization to get projects from.",
type: "The type of project to filter by."
},
LIST_GROUPS: {
organizationId: "The ID of the organization to list groups for."

View File

@@ -220,6 +220,7 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
id: true,
name: true,
description: true,
type: true,
slug: true,
autoCapitalization: true,
orgId: true,

View File

@@ -5,6 +5,7 @@ import {
ProjectMembershipsSchema,
ProjectRolesSchema,
ProjectSlackConfigsSchema,
ProjectType,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -135,7 +136,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
includeRoles: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.transform((value) => value === "true"),
type: z.nativeEnum(ProjectType).optional()
}),
response: {
200: z.object({
@@ -154,7 +156,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId
actorOrgId: req.permission.orgId,
type: req.query.type
});
return { workspaces };
}

View File

@@ -5,6 +5,7 @@ import {
OrgMembershipsSchema,
ProjectMembershipsSchema,
ProjectsSchema,
ProjectType,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -76,7 +77,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId)
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId),
type: z.nativeEnum(ProjectType).optional().describe(ORGANIZATIONS.GET_PROJECTS.type)
}),
response: {
200: z.object({

View File

@@ -15,7 +15,6 @@ import {
TProjectUserMembershipRolesInsert,
TUsers
} from "@app/db/schemas";
import { TProjects } from "@app/db/schemas/projects";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
@@ -196,26 +195,18 @@ export const orgServiceFactory = ({
return org;
};
const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => {
const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id));
let workspaces: (TProjects & { organization: string } & {
environments: {
id: string;
slug: string;
name: string;
}[];
})[];
const findAllWorkspaces = async ({ actor, actorId, orgId, type }: TFindAllWorkspacesDTO) => {
if (actor === ActorType.USER) {
workspaces = await projectDAL.findAllProjects(actorId);
} else if (actor === ActorType.IDENTITY) {
workspaces = await projectDAL.findAllProjectsByIdentity(actorId);
} else {
throw new BadRequestError({ message: "Invalid actor type" });
const workspaces = await projectDAL.findAllProjects(actorId, orgId, type);
return workspaces;
}
return workspaces.filter((workspace) => organizationWorkspaceIds.has(workspace.id));
if (actor === ActorType.IDENTITY) {
const workspaces = await projectDAL.findAllProjectsByIdentity(actorId, type);
return workspaces;
}
throw new BadRequestError({ message: "Invalid actor type" });
};
const addGhostUser = async (orgId: string, tx?: Knex) => {

View File

@@ -1,6 +1,7 @@
import { TOrgPermission } from "@app/lib/types";
import { ActorAuthMethod, ActorType, MfaMethod } from "../auth/auth-type";
import { ProjectType } from "@app/db/schemas";
export type TUpdateOrgMembershipDTO = {
userId: string;
@@ -55,6 +56,7 @@ export type TFindAllWorkspacesDTO = {
actorOrgId: string | undefined;
actorAuthMethod: ActorAuthMethod;
orgId: string;
type?: ProjectType;
};
export type TUpdateOrgDTO = {

View File

@@ -1,7 +1,14 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { ProjectsSchema, ProjectUpgradeStatus, ProjectVersion, TableName, TProjectsUpdate } from "@app/db/schemas";
import {
ProjectsSchema,
ProjectType,
ProjectUpgradeStatus,
ProjectVersion,
TableName,
TProjectsUpdate
} from "@app/db/schemas";
import { BadRequestError, DatabaseError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
@@ -12,12 +19,18 @@ export type TProjectDALFactory = ReturnType<typeof projectDALFactory>;
export const projectDALFactory = (db: TDbClient) => {
const projectOrm = ormify(db, TableName.Project);
const findAllProjects = async (userId: string) => {
const findAllProjects = async (userId: string, orgId: string, projectType?: ProjectType | null) => {
try {
const workspaces = await db
.replicaNode()(TableName.ProjectMembership)
.where({ userId })
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.orgId`, orgId)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);
}
})
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
@@ -31,14 +44,17 @@ export const projectDALFactory = (db: TDbClient) => {
{ column: `${TableName.Environment}.position`, order: "asc" }
]);
const groups: string[] = await db(TableName.UserGroupMembership)
.where({ userId })
.select(selectAllTableCols(TableName.UserGroupMembership))
.pluck("groupId");
const groups = db(TableName.UserGroupMembership).where({ userId }).select("groupId");
const groupWorkspaces = await db(TableName.GroupProjectMembership)
.whereIn("groupId", groups)
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.orgId`, orgId)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);
}
})
.whereNotIn(
`${TableName.Project}.id`,
workspaces.map(({ id }) => id)
@@ -108,12 +124,13 @@ export const projectDALFactory = (db: TDbClient) => {
}
};
const findAllProjectsByIdentity = async (identityId: string) => {
const findAllProjectsByIdentity = async (identityId: string, projectType?: ProjectType) => {
try {
const workspaces = await db
.replicaNode()(TableName.IdentityProjectMembership)
.where({ identityId })
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.type`, projectType)
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),

View File

@@ -157,7 +157,6 @@ export const projectServiceFactory = ({
type = ProjectType.SecretManager
}: TCreateProjectDTO) => {
const organization = await orgDAL.findOne({ id: actorOrgId });
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
actor,
actorId,
@@ -432,8 +431,8 @@ export const projectServiceFactory = ({
return deletedProject;
};
const getProjects = async ({ actorId, includeRoles, actorAuthMethod, actorOrgId }: TListProjectsDTO) => {
const workspaces = await projectDAL.findAllProjects(actorId);
const getProjects = async ({ actorId, includeRoles, actorAuthMethod, actorOrgId, type }: TListProjectsDTO) => {
const workspaces = await projectDAL.findAllProjects(actorId, actorOrgId, type);
if (includeRoles) {
const { permission } = await permissionService.getUserOrgPermission(actorId, actorOrgId, actorAuthMethod);

View File

@@ -85,6 +85,7 @@ export type TDeleteProjectDTO = {
export type TListProjectsDTO = {
includeRoles: boolean;
type?: ProjectType | null;
} & Omit<TProjectPermission, "projectId">;
export type TUpgradeProjectDTO = {

View File

@@ -41,6 +41,7 @@ import {
} from "@app/hooks/api";
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
import { ProjectType } from "@app/hooks/api/workspace/types";
const formSchema = z.object({
name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
@@ -59,11 +60,12 @@ type TAddProjectFormData = z.infer<typeof formSchema>;
interface NewProjectModalProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
projectType: ProjectType;
}
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange">;
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange" | "projectType">;
const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
const router = useRouter();
const { currentOrg } = useOrganization();
const { permission } = useOrgPermission();
@@ -124,7 +126,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
projectName: name,
projectDescription: description,
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
template
template,
type: projectType
});
if (addMembers) {
@@ -145,7 +148,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
createNotification({ text: "Project created", type: "success" });
reset();
onOpenChange(false);
router.push(`/project/${newProjectId}/secrets/overview`);
router.push(`/project/${newProjectId}/${projectType}/overview`);
} catch (err) {
console.error(err);
createNotification({ text: "Failed to create project", type: "error" });
@@ -316,14 +319,18 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
);
};
export const NewProjectModal: FC<NewProjectModalProps> = ({ isOpen, onOpenChange }) => {
export const NewProjectModal: FC<NewProjectModalProps> = ({
isOpen,
onOpenChange,
projectType
}) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Create a new project"
subTitle="This project will contain your secrets and configurations."
>
<NewProjectForm onOpenChange={onOpenChange} />
<NewProjectForm onOpenChange={onOpenChange} projectType={projectType} />
</ModalContent>
</Modal>
);

View File

@@ -41,6 +41,7 @@ export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
// handle redirects for project-specific routes
useEffect(() => {
if (shouldTriggerNoProjectAccess) {
console.log(value, workspaceId);
createNotification({
text: "You are not a member of this project.",
type: "info"

View File

@@ -1,5 +1,6 @@
import { apiRequest } from "@app/config/request";
import { createWorkspace } from "@app/hooks/api/workspace/queries";
import { ProjectType } from "@app/hooks/api/workspace/types";
const secretsToBeAdded = [
{
@@ -41,7 +42,8 @@ const initProjectHelper = async ({ projectName }: { projectName: string }) => {
const {
data: { project }
} = await createWorkspace({
projectName
projectName,
type: ProjectType.SecretManager
});
try {

View File

@@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { workspaceKeys } from "../workspace";
import { ProjectType } from "../workspace/types";
export const useImportEnvKey = () => {
const queryClient = useQueryClient();
@@ -31,7 +32,7 @@ export const useImportEnvKey = () => {
}
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
});
};

View File

@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
import { userKeys } from "../users/query-keys";
import { workspaceKeys } from "./query-keys";
import { TUpdateWorkspaceGroupRoleDTO } from "./types";
import { ProjectType, TUpdateWorkspaceGroupRoleDTO } from "./types";
export const useAddGroupToWorkspace = () => {
const queryClient = useQueryClient();
@@ -83,7 +83,7 @@ export const useLeaveProject = () => {
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`);
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace());
}
});
};
@@ -95,7 +95,7 @@ export const useMigrateProjectToV3 = () => {
return apiRequest.post(`/api/v1/workspace/${workspaceId}/migrate-v3`);
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
});
};

View File

@@ -26,6 +26,7 @@ import {
DeleteWorkspaceDTO,
NameWorkspaceSecretsDTO,
ProjectIdentityOrderBy,
ProjectType,
TGetUpgradeProjectStatusDTO,
TListProjectIdentitiesDTO,
ToggleAutoCapitalizationDTO,
@@ -82,7 +83,7 @@ export const useUpgradeProject = () => {
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
});
};
@@ -102,10 +103,11 @@ export const useGetUpgradeProjectStatus = ({
});
};
const fetchUserWorkspaces = async (includeRoles?: boolean) => {
const fetchUserWorkspaces = async (includeRoles?: boolean, type?: ProjectType) => {
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>("/api/v1/workspace", {
params: {
includeRoles
includeRoles,
type
}
});
return data.workspaces;
@@ -139,8 +141,16 @@ export const useGetWorkspaceById = (
});
};
export const useGetUserWorkspaces = (includeRoles?: boolean) =>
useQuery(workspaceKeys.getAllUserWorkspace, () => fetchUserWorkspaces(includeRoles));
export const useGetUserWorkspaces = ({
includeRoles,
type
}: {
includeRoles?: boolean;
type?: ProjectType;
} = {}) =>
useQuery(workspaceKeys.getAllUserWorkspace(type || ""), () =>
fetchUserWorkspaces(includeRoles, type)
);
const fetchUserWorkspaceMemberships = async (orgId: string) => {
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
@@ -206,33 +216,26 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
refetchInterval: 4000
});
export const createWorkspace = ({
projectName,
projectDescription,
kmsKeyId,
template
}: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => {
return apiRequest.post("/api/v2/workspace", {
projectName,
projectDescription,
kmsKeyId,
template
});
export const createWorkspace = (
dto: CreateWorkspaceDTO
): Promise<{ data: { project: Workspace } }> => {
return apiRequest.post("/api/v2/workspace", dto);
};
export const useCreateWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
createWorkspace({
projectName,
projectDescription,
kmsKeyId,
template
template,
type
}),
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.data.project.type));
}
});
};
@@ -240,15 +243,19 @@ export const useCreateWorkspace = () => {
export const useUpdateProject = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateProjectDTO>({
mutationFn: ({ projectID, newProjectName, newProjectDescription }) => {
return apiRequest.patch(`/api/v1/workspace/${projectID}`, {
name: newProjectName,
description: newProjectDescription
});
return useMutation<Workspace, {}, UpdateProjectDTO>({
mutationFn: async ({ projectID, newProjectName, newProjectDescription }) => {
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
`/api/v1/workspace/${projectID}`,
{
name: newProjectName,
description: newProjectDescription
}
);
return data.workspace;
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
}
});
};
@@ -256,13 +263,18 @@ export const useUpdateProject = () => {
export const useToggleAutoCapitalization = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({
mutationFn: ({ workspaceID, state }) =>
apiRequest.post(`/api/v1/workspace/${workspaceID}/auto-capitalization`, {
autoCapitalization: state
}),
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
return useMutation<Workspace, {}, ToggleAutoCapitalizationDTO>({
mutationFn: async ({ workspaceID, state }) => {
const { data } = await apiRequest.post<{ workspace: Workspace }>(
`/api/v1/workspace/${workspaceID}/auto-capitalization`,
{
autoCapitalization: state
}
);
return data.workspace;
},
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
}
});
};
@@ -270,14 +282,15 @@ export const useToggleAutoCapitalization = () => {
export const useUpdateWorkspaceVersionLimit = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
mutationFn: ({ projectSlug, pitVersionLimit }) => {
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
return useMutation<Workspace, {}, UpdatePitVersionLimitDTO>({
mutationFn: async ({ projectSlug, pitVersionLimit }) => {
const { data } = await apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
pitVersionLimit
});
return data.workspace;
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
}
});
};
@@ -285,14 +298,18 @@ export const useUpdateWorkspaceVersionLimit = () => {
export const useUpdateWorkspaceAuditLogsRetention = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateAuditLogsRetentionDTO>({
mutationFn: ({ projectSlug, auditLogsRetentionDays }) => {
return apiRequest.put(`/api/v1/workspace/${projectSlug}/audit-logs-retention`, {
auditLogsRetentionDays
});
return useMutation<Workspace, {}, UpdateAuditLogsRetentionDTO>({
mutationFn: async ({ projectSlug, auditLogsRetentionDays }) => {
const { data } = await apiRequest.put(
`/api/v1/workspace/${projectSlug}/audit-logs-retention`,
{
auditLogsRetentionDays
}
);
return data.workspace;
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
}
});
};
@@ -300,12 +317,13 @@ export const useUpdateWorkspaceAuditLogsRetention = () => {
export const useDeleteWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeleteWorkspaceDTO>({
mutationFn: ({ workspaceID }) => {
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
return useMutation<Workspace, {}, DeleteWorkspaceDTO>({
mutationFn: async ({ workspaceID }) => {
const { data } = await apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
return data.workspace;
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
queryClient.invalidateQueries(["org-admin-projects"]);
}
});
@@ -322,7 +340,7 @@ export const useCreateWsEnvironment = () => {
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
});
};
@@ -339,7 +357,7 @@ export const useUpdateWsEnvironment = () => {
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
});
};
@@ -352,7 +370,7 @@ export const useDeleteWsEnvironment = () => {
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/environments/${id}`);
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
});
};

View File

@@ -11,7 +11,8 @@ export const workspaceKeys = {
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: ["workspaces"] as const,
getAllUserWorkspace: (type?: string) =>
type ? ["workspaces", { type }] : (["workspace"] as const),
getWorkspaceAuditLogs: (workspaceId: string) =>
[{ workspaceId }, "workspace-audit-logs"] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,

View File

@@ -8,6 +8,12 @@ export enum ProjectVersion {
V3 = 3
}
export enum ProjectType {
SecretManager = "secret-manager",
CertificateManager = "cert-manager",
Cmek = "cmek"
}
export enum ProjectUserMembershipTemporaryMode {
Relative = "relative"
}
@@ -16,6 +22,7 @@ export type Workspace = {
__v: number;
id: string;
name: string;
type: ProjectType;
description?: string;
orgId: string;
version: ProjectVersion;
@@ -59,6 +66,7 @@ export type CreateWorkspaceDTO = {
projectDescription?: string;
kmsKeyId?: string;
template?: string;
type: ProjectType;
};
export type UpdateProjectDTO = {

View File

@@ -163,7 +163,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
!router.asPath.includes("secret-scanning") &&
!router.asPath.includes("integration")))
) {
router.push(`/org/${currentOrg?.id}/overview`);
router.push(`/org/${currentOrg?.id}/secret-manager/overview`);
}
// else if (!router.asPath.includes("org") && !router.asPath.includes("project") && !router.asPath.includes("integrations") && !router.asPath.includes("personal-settings")) {
@@ -216,7 +216,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
<div className="flex h-12 cursor-default items-center px-3 pt-6">
{(router.asPath.includes("project") ||
router.asPath.includes("integrations")) && (
<Link href={`/org/${currentOrg?.id}/overview`}>
<Link href={`/org/${currentOrg?.id}/${currentWorkspace?.type}/overview`}>
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} />
</div>
@@ -379,7 +379,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
(!router.asPath.includes("personal") && currentWorkspace ? (
<ProjectSelect />
) : (
<Link href={`/org/${currentOrg?.id}/overview`}>
<Link href={`/org/${currentOrg?.id}/${currentWorkspace?.type}/overview`}>
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
Back to organization
@@ -493,13 +493,33 @@ export const AppLayout = ({ children }: LayoutProps) => {
</Menu>
) : (
<Menu className="mt-4">
<Link href={`/org/${currentOrg?.id}/overview`} passHref>
<Link href={`/org/${currentOrg?.id}/secret-manager/overview`} passHref>
<a>
<MenuItem
isSelected={router.asPath.includes("/overview")}
isSelected={router.asPath.includes("/secret-manager/overview")}
icon="system-outline-165-view-carousel"
>
Overview
Secret Manager
</MenuItem>
</a>
</Link>
<Link href={`/org/${currentOrg?.id}/cert-manager/overview`} passHref>
<a>
<MenuItem
isSelected={router.asPath.includes("/cert-manager/overview")}
icon="system-outline-165-view-carousel"
>
Cert Manager
</MenuItem>
</a>
</Link>
<Link href={`/org/${currentOrg?.id}/cmek/overview`} passHref>
<a>
<MenuItem
isSelected={router.asPath.includes("/cmek/overview")}
icon="system-outline-165-view-carousel"
>
Cmek
</MenuItem>
</a>
</Link>

View File

@@ -1,343 +0,0 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable react/jsx-key */
import { Fragment, useMemo } from "react";
import { useTranslation } from "react-i18next";
import Image from "next/image";
import { useRouter } from "next/router";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
import {
faAngleDown,
faBook,
faCoins,
faEnvelope,
faGear,
faPlus,
faRightFromBracket
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
import { TFunction } from "i18next";
import guidGenerator from "@app/components/utilities/randomId";
import { useOrganization, useSubscription, useUser } from "@app/context";
import { useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
const supportOptions = (t: TFunction) => [
[
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faSlack} />,
t("nav.support.slack"),
"https://infisical.com/slack"
],
[
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faBook} />,
t("nav.support.docs"),
"https://infisical.com/docs/documentation/getting-started/introduction"
],
[
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faGithub} />,
t("nav.support.issue"),
"https://github.com/Infisical/infisical-cli/issues"
],
[
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faEnvelope} />,
t("nav.support.email"),
"mailto:support@infisical.com"
]
];
export interface ICurrentOrg {
name: string;
}
export interface IUser {
firstName: string;
lastName: string;
email: string;
}
/**
* This is the navigation bar in the main app.
* It has two main components: support options and user menu (inlcudes billing, logout, org/user settings)
* @returns NavBar
*/
export const Navbar = () => {
const router = useRouter();
const { subscription } = useSubscription();
const { currentOrg, orgs } = useOrganization();
const { mutateAsync } = useGetOrgTrialUrl();
const { user } = useUser();
const logout = useLogoutUser();
const { t } = useTranslation();
// remove this memo
const supportOptionsList = useMemo(() => supportOptions(t), [t]);
const closeApp = async () => {
try {
console.log("Logging out...");
await logout.mutateAsync();
localStorage.removeItem("protectedKey");
localStorage.removeItem("protectedKeyIV");
localStorage.removeItem("protectedKeyTag");
localStorage.removeItem("publicKey");
localStorage.removeItem("encryptedPrivateKey");
localStorage.removeItem("iv");
localStorage.removeItem("tag");
localStorage.removeItem("PRIVATE_KEY");
localStorage.removeItem("orgData.id");
localStorage.removeItem("projectData.id");
router.push("/login");
} catch (error) {
console.error(error);
}
};
return (
<div className="z-[70] border-b border-mineshaft-500 bg-mineshaft-900 text-white">
<div className="flex w-full justify-between px-4">
<div className="flex flex-row items-center">
<div className="flex justify-center py-4">
<Image src="/images/logotransparent.png" height={23} width={57} alt="logo" />
</div>
<a href="#" className="mx-2 text-2xl font-semibold text-white">
Infisical
</a>
</div>
<div className="relative z-40 mx-2 flex items-center justify-start">
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mr-4 flex items-center rounded-md px-3 py-2 text-sm text-gray-200 duration-200 hover:bg-white/10"
>
<FontAwesomeIcon icon={faBook} className="mr-2 text-xl" />
Docs
</a>
<Menu as="div" className="relative inline-block text-left">
<div className="mr-4">
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
<FontAwesomeIcon className="text-xl" icon={faCircleQuestion} />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-20 mt-0.5 w-64 origin-top-right rounded-md border border-mineshaft-700 bg-bunker px-2 py-1.5 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{supportOptionsList.map(([icon, text, url]) => (
<a
key={guidGenerator()}
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md py-0.5 font-normal text-gray-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md py-2 px-2 text-gray-400 duration-200 hover:bg-white/10 hover:text-gray-200">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
))}
</Menu.Items>
</Transition>
</Menu>
<Menu as="div" className="relative mr-4 inline-block text-left">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md py-2 pr-2 pl-2 text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
{user?.firstName} {user?.lastName}
<FontAwesomeIcon
icon={faAngleDown}
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="w-68 absolute right-0 z-[125] mt-0.5 origin-top-right divide-y divide-mineshaft-700 rounded-md border border-mineshaft-700 bg-mineshaft-900 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-2xl focus:outline-none">
<div className="px-1 py-1">
<div className="ml-2 mt-2 self-start text-xs font-semibold tracking-wide text-gray-400">
{t("nav.user.signed-in-as")}
</div>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => router.push("/personal-settings")}
className="mx-1 my-1 flex cursor-pointer flex-row items-center rounded-md px-1 hover:bg-white/5"
>
<div className="flex h-8 w-9 items-center justify-center rounded-full bg-white/10 text-gray-300">
{user?.firstName?.charAt(0)}
</div>
<div className="flex w-full items-center justify-between">
<div>
<p className="px-2 pt-1 text-sm text-gray-300">
{" "}
{user?.firstName} {user?.lastName}
</p>
<p className="px-2 pb-1 text-xs text-gray-400">{user?.email}</p>
</div>
<FontAwesomeIcon
icon={faGear}
className="mr-1 cursor-pointer rounded-md p-2 text-lg text-gray-400 hover:bg-white/10"
/>
</div>
</div>
</div>
<div className="px-2 pt-2">
<div className="ml-2 mt-2 self-start text-xs font-semibold tracking-wide text-gray-400">
{t("nav.user.current-organization")}
</div>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => router.push(`/settings/org/${router.query.id}`)}
className="mt-2 flex cursor-pointer flex-row items-center rounded-md px-2 py-1 hover:bg-white/5"
>
<div className="flex h-7 w-8 items-center justify-center rounded-md bg-white/10 text-gray-300">
{currentOrg?.name?.charAt(0)}
</div>
<div className="flex w-full items-center justify-between">
<p className="px-2 text-sm text-gray-300">{currentOrg?.name}</p>
<FontAwesomeIcon
icon={faGear}
className="cursor-pointer rounded-md p-2 text-lg text-gray-400 hover:bg-white/10"
/>
</div>
</div>
{subscription && subscription.slug !== null && (
<button
// onClick={buttonAction}
type="button"
className="w-full cursor-pointer"
>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => router.push(`/settings/billing/${router.query.id}`)}
className="relative mt-1 flex cursor-pointer select-none justify-start rounded-md py-2 px-2 text-gray-400 duration-200 hover:bg-white/5 hover:text-gray-200"
>
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faCoins} />
<div className="text-sm">{t("nav.user.usage-billing")}</div>
</div>
</button>
)}
<button
type="button"
// onClick={buttonAction}
className="mb-2 w-full cursor-pointer"
>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => router.push(`/settings/org/${router.query.id}?invite`)}
className="relative mt-1 flex cursor-pointer select-none justify-start rounded-md py-2 pl-10 pr-4 text-gray-400 duration-200 hover:bg-primary/100 hover:font-semibold hover:text-black"
>
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="ml-1" />
</span>
<div className="ml-1 text-sm">{t("nav.user.invite")}</div>
</div>
</button>
</div>
{orgs && orgs?.length > 1 && (
<div className="px-1 pt-1">
<div className="ml-2 mt-2 self-start text-xs font-semibold tracking-wide text-gray-400">
{t("nav.user.other-organizations")}
</div>
<div className="mt-3 mb-2 flex flex-col items-start px-1">
{orgs
?.filter((org: { id: string }) => org.id !== currentOrg?.id)
.map((org: { id: string; name: string }) => (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
key={guidGenerator()}
onClick={() => {
localStorage.setItem("orgData.id", org.id);
router.reload();
}}
className="flex w-full cursor-pointer flex-row items-center justify-start rounded-md p-1.5 hover:bg-white/5"
>
<div className="flex h-7 w-8 items-center justify-center rounded-md bg-white/10 text-gray-300">
{org.name.charAt(0)}
</div>
<div className="flex w-full items-center justify-between">
<p className="px-2 text-sm text-gray-300">{org.name}</p>
</div>
</div>
))}
</div>
</div>
)}
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (
<button
type="button"
onClick={closeApp}
className={`${
active ? "bg-red font-semibold text-white" : "text-gray-400"
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
<div className="relative flex cursor-pointer select-none items-center justify-start">
<FontAwesomeIcon
className="ml-1.5 mr-3 text-lg"
icon={faRightFromBracket}
/>
{t("common.logout")}
</div>
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
<div className="mx-auto w-full border-t border-mineshaft-500 text-center">
<button
type="button"
onClick={async () => {
if (!subscription || !currentOrg) return;
// direct user to start pro trial
const url = await mutateAsync({
orgId: currentOrg.id,
success_url: window.location.href
});
window.location.href = url;
}}
className="mx-auto py-4 text-center text-sm"
>
You are currently on the <span className="font-semibold">Starter</span> plan. Unlock the
full power of Infisical on the{" "}
<span className="font-semibold">Pro Free Trial &rarr;</span>
</button>
</div>
)}
</div>
);
};

View File

@@ -19,7 +19,7 @@ import {
import { usePopUp } from "@app/hooks";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
import { Workspace } from "@app/hooks/api/workspace/types";
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
type TWorkspaceWithFaveProp = Workspace & { isFavorite: boolean };
@@ -138,6 +138,7 @@ export const ProjectSelect = () => {
const { options, value } = useMemo(() => {
const projectOptions = workspaces
.filter((el) => el.type === currentWorkspace?.type)
.map((w): Workspace & { isFavorite: boolean } => ({
...w,
isFavorite: Boolean(projectFavorites?.includes(w.id))
@@ -206,6 +207,7 @@ export const ProjectSelect = () => {
<NewProjectModal
isOpen={popUp.addNewWs.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
projectType={currentWorkspace?.type || ProjectType.SecretManager}
/>
</div>
);

View File

@@ -0,0 +1,8 @@
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProductOverview } from "../secret-manager/overview";
const CertManagerOverviewPage = () => <ProductOverview type={ProjectType.CertificateManager} />;
Object.assign(CertManagerOverviewPage, { requireAuth: true });
export default CertManagerOverviewPage;

View File

@@ -0,0 +1,8 @@
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProductOverview } from "../secret-manager/overview";
const CmekManagerOverviewPage = () => <ProductOverview type={ProjectType.Cmek} />;
Object.assign(CmekManagerOverviewPage, { requireAuth: true });
export default CmekManagerOverviewPage;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,9 @@ export const AddOrgMemberModal = ({
const { data: organizationRoles } = useGetOrgRoles(currentOrg?.id ?? "");
const { data: serverDetails } = useFetchServerStatus();
const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg();
const { data: projects, isLoading: isProjectsLoading } = useGetUserWorkspaces(true);
const { data: projects, isLoading: isProjectsLoading } = useGetUserWorkspaces({
includeRoles: true
});
const {
control,

View File

@@ -12,7 +12,7 @@ import { useProjectPermission, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetWorkspaceById, useMigrateProjectToV3, workspaceKeys } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
enum ProjectUpgradeStatus {
InProgress = "IN_PROGRESS",
@@ -53,7 +53,7 @@ export const SecretV2MigrationSection = () => {
if (isProjectUpgraded && migrateProjectToV3.data) {
createNotification({ type: "success", text: "Project upgrade completed successfully" });
migrateProjectToV3.reset();
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
}
}, [isProjectUpgraded, Boolean(migrateProjectToV3.data)]);