mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 03:26:12 +00:00
feat: completed migration
This commit is contained in:
@@ -1,13 +1,248 @@
|
|||||||
import { Knex } from "knex";
|
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> {
|
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");
|
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
|
||||||
if (!hasTypeColumn) {
|
if (!hasTypeColumn) {
|
||||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
t.string("type");
|
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");
|
t.dropColumn("type");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasSplitMappingTable = await knex.schema.hasTable(TableName.ProjectSplitBackfillIds);
|
||||||
|
if (hasSplitMappingTable) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectSplitBackfillIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export enum TableName {
|
|||||||
SecretApprovalRequestSecretV2 = "secret_approval_requests_secrets_v2",
|
SecretApprovalRequestSecretV2 = "secret_approval_requests_secrets_v2",
|
||||||
SecretApprovalRequestSecretTagV2 = "secret_approval_request_secret_tags_v2",
|
SecretApprovalRequestSecretTagV2 = "secret_approval_request_secret_tags_v2",
|
||||||
SnapshotSecretV2 = "secret_snapshot_secrets_v2",
|
SnapshotSecretV2 = "secret_snapshot_secrets_v2",
|
||||||
|
ProjectSplitBackfillIds = "project_split_backfill_ids",
|
||||||
// junction tables with tags
|
// junction tables with tags
|
||||||
SecretV2JnTag = "secret_v2_tag_junction",
|
SecretV2JnTag = "secret_v2_tag_junction",
|
||||||
JnSecretTag = "secret_tag_junction",
|
JnSecretTag = "secret_tag_junction",
|
||||||
|
|||||||
@@ -428,7 +428,8 @@ export const ORGANIZATIONS = {
|
|||||||
search: "The text string that identity membership names will be filtered by."
|
search: "The text string that identity membership names will be filtered by."
|
||||||
},
|
},
|
||||||
GET_PROJECTS: {
|
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: {
|
LIST_GROUPS: {
|
||||||
organizationId: "The ID of the organization to list groups for."
|
organizationId: "The ID of the organization to list groups for."
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
|||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
description: true,
|
description: true,
|
||||||
|
type: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
autoCapitalization: true,
|
autoCapitalization: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
ProjectRolesSchema,
|
ProjectRolesSchema,
|
||||||
ProjectSlackConfigsSchema,
|
ProjectSlackConfigsSchema,
|
||||||
|
ProjectType,
|
||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -135,7 +136,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
includeRoles: z
|
includeRoles: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("false")
|
.default("false")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true"),
|
||||||
|
type: z.nativeEnum(ProjectType).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -154,7 +156,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId,
|
||||||
|
type: req.query.type
|
||||||
});
|
});
|
||||||
return { workspaces };
|
return { workspaces };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
OrgMembershipsSchema,
|
OrgMembershipsSchema,
|
||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
ProjectsSchema,
|
ProjectsSchema,
|
||||||
|
ProjectType,
|
||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -76,7 +77,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
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: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
TProjectUserMembershipRolesInsert,
|
TProjectUserMembershipRolesInsert,
|
||||||
TUsers
|
TUsers
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TProjects } from "@app/db/schemas/projects";
|
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
@@ -196,26 +195,18 @@ export const orgServiceFactory = ({
|
|||||||
return org;
|
return org;
|
||||||
};
|
};
|
||||||
|
|
||||||
const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => {
|
const findAllWorkspaces = async ({ actor, actorId, orgId, type }: 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;
|
|
||||||
}[];
|
|
||||||
})[];
|
|
||||||
|
|
||||||
if (actor === ActorType.USER) {
|
if (actor === ActorType.USER) {
|
||||||
workspaces = await projectDAL.findAllProjects(actorId);
|
const workspaces = await projectDAL.findAllProjects(actorId, orgId, type);
|
||||||
} else if (actor === ActorType.IDENTITY) {
|
return workspaces;
|
||||||
workspaces = await projectDAL.findAllProjectsByIdentity(actorId);
|
|
||||||
} else {
|
|
||||||
throw new BadRequestError({ message: "Invalid actor type" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => {
|
const addGhostUser = async (orgId: string, tx?: Knex) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ActorAuthMethod, ActorType, MfaMethod } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType, MfaMethod } from "../auth/auth-type";
|
||||||
|
import { ProjectType } from "@app/db/schemas";
|
||||||
|
|
||||||
export type TUpdateOrgMembershipDTO = {
|
export type TUpdateOrgMembershipDTO = {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -55,6 +56,7 @@ export type TFindAllWorkspacesDTO = {
|
|||||||
actorOrgId: string | undefined;
|
actorOrgId: string | undefined;
|
||||||
actorAuthMethod: ActorAuthMethod;
|
actorAuthMethod: ActorAuthMethod;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
type?: ProjectType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateOrgDTO = {
|
export type TUpdateOrgDTO = {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
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 { BadRequestError, DatabaseError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
@@ -12,12 +19,18 @@ export type TProjectDALFactory = ReturnType<typeof projectDALFactory>;
|
|||||||
export const projectDALFactory = (db: TDbClient) => {
|
export const projectDALFactory = (db: TDbClient) => {
|
||||||
const projectOrm = ormify(db, TableName.Project);
|
const projectOrm = ormify(db, TableName.Project);
|
||||||
|
|
||||||
const findAllProjects = async (userId: string) => {
|
const findAllProjects = async (userId: string, orgId: string, projectType?: ProjectType | null) => {
|
||||||
try {
|
try {
|
||||||
const workspaces = await db
|
const workspaces = await db
|
||||||
.replicaNode()(TableName.ProjectMembership)
|
.replicaNode()(TableName.ProjectMembership)
|
||||||
.where({ userId })
|
.where({ userId })
|
||||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.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`)
|
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
|
||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.Project),
|
selectAllTableCols(TableName.Project),
|
||||||
@@ -31,14 +44,17 @@ export const projectDALFactory = (db: TDbClient) => {
|
|||||||
{ column: `${TableName.Environment}.position`, order: "asc" }
|
{ column: `${TableName.Environment}.position`, order: "asc" }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const groups: string[] = await db(TableName.UserGroupMembership)
|
const groups = db(TableName.UserGroupMembership).where({ userId }).select("groupId");
|
||||||
.where({ userId })
|
|
||||||
.select(selectAllTableCols(TableName.UserGroupMembership))
|
|
||||||
.pluck("groupId");
|
|
||||||
|
|
||||||
const groupWorkspaces = await db(TableName.GroupProjectMembership)
|
const groupWorkspaces = await db(TableName.GroupProjectMembership)
|
||||||
.whereIn("groupId", groups)
|
.whereIn("groupId", groups)
|
||||||
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.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(
|
.whereNotIn(
|
||||||
`${TableName.Project}.id`,
|
`${TableName.Project}.id`,
|
||||||
workspaces.map(({ id }) => 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 {
|
try {
|
||||||
const workspaces = await db
|
const workspaces = await db
|
||||||
.replicaNode()(TableName.IdentityProjectMembership)
|
.replicaNode()(TableName.IdentityProjectMembership)
|
||||||
.where({ identityId })
|
.where({ identityId })
|
||||||
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.where(`${TableName.Project}.type`, projectType)
|
||||||
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
|
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
|
||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.Project),
|
selectAllTableCols(TableName.Project),
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ export const projectServiceFactory = ({
|
|||||||
type = ProjectType.SecretManager
|
type = ProjectType.SecretManager
|
||||||
}: TCreateProjectDTO) => {
|
}: TCreateProjectDTO) => {
|
||||||
const organization = await orgDAL.findOne({ id: actorOrgId });
|
const organization = await orgDAL.findOne({ id: actorOrgId });
|
||||||
|
|
||||||
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
|
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -432,8 +431,8 @@ export const projectServiceFactory = ({
|
|||||||
return deletedProject;
|
return deletedProject;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjects = async ({ actorId, includeRoles, actorAuthMethod, actorOrgId }: TListProjectsDTO) => {
|
const getProjects = async ({ actorId, includeRoles, actorAuthMethod, actorOrgId, type }: TListProjectsDTO) => {
|
||||||
const workspaces = await projectDAL.findAllProjects(actorId);
|
const workspaces = await projectDAL.findAllProjects(actorId, actorOrgId, type);
|
||||||
|
|
||||||
if (includeRoles) {
|
if (includeRoles) {
|
||||||
const { permission } = await permissionService.getUserOrgPermission(actorId, actorOrgId, actorAuthMethod);
|
const { permission } = await permissionService.getUserOrgPermission(actorId, actorOrgId, actorAuthMethod);
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export type TDeleteProjectDTO = {
|
|||||||
|
|
||||||
export type TListProjectsDTO = {
|
export type TListProjectsDTO = {
|
||||||
includeRoles: boolean;
|
includeRoles: boolean;
|
||||||
|
type?: ProjectType | null;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpgradeProjectDTO = {
|
export type TUpgradeProjectDTO = {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
|
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
|
||||||
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
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 {
|
interface NewProjectModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: (isOpen: boolean) => void;
|
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 router = useRouter();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { permission } = useOrgPermission();
|
const { permission } = useOrgPermission();
|
||||||
@@ -124,7 +126,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
|||||||
projectName: name,
|
projectName: name,
|
||||||
projectDescription: description,
|
projectDescription: description,
|
||||||
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
|
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
|
||||||
template
|
template,
|
||||||
|
type: projectType
|
||||||
});
|
});
|
||||||
|
|
||||||
if (addMembers) {
|
if (addMembers) {
|
||||||
@@ -145,7 +148,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
|||||||
createNotification({ text: "Project created", type: "success" });
|
createNotification({ text: "Project created", type: "success" });
|
||||||
reset();
|
reset();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
router.push(`/project/${newProjectId}/secrets/overview`);
|
router.push(`/project/${newProjectId}/${projectType}/overview`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createNotification({ text: "Failed to create project", type: "error" });
|
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 (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Create a new project"
|
title="Create a new project"
|
||||||
subTitle="This project will contain your secrets and configurations."
|
subTitle="This project will contain your secrets and configurations."
|
||||||
>
|
>
|
||||||
<NewProjectForm onOpenChange={onOpenChange} />
|
<NewProjectForm onOpenChange={onOpenChange} projectType={projectType} />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
|
|||||||
// handle redirects for project-specific routes
|
// handle redirects for project-specific routes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldTriggerNoProjectAccess) {
|
if (shouldTriggerNoProjectAccess) {
|
||||||
|
console.log(value, workspaceId);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "You are not a member of this project.",
|
text: "You are not a member of this project.",
|
||||||
type: "info"
|
type: "info"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
const secretsToBeAdded = [
|
const secretsToBeAdded = [
|
||||||
{
|
{
|
||||||
@@ -41,7 +42,8 @@ const initProjectHelper = async ({ projectName }: { projectName: string }) => {
|
|||||||
const {
|
const {
|
||||||
data: { project }
|
data: { project }
|
||||||
} = await createWorkspace({
|
} = await createWorkspace({
|
||||||
projectName
|
projectName,
|
||||||
|
type: ProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
|
import { ProjectType } from "../workspace/types";
|
||||||
|
|
||||||
export const useImportEnvKey = () => {
|
export const useImportEnvKey = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -31,7 +32,7 @@ export const useImportEnvKey = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import { userKeys } from "../users/query-keys";
|
import { userKeys } from "../users/query-keys";
|
||||||
import { workspaceKeys } from "./query-keys";
|
import { workspaceKeys } from "./query-keys";
|
||||||
import { TUpdateWorkspaceGroupRoleDTO } from "./types";
|
import { ProjectType, TUpdateWorkspaceGroupRoleDTO } from "./types";
|
||||||
|
|
||||||
export const useAddGroupToWorkspace = () => {
|
export const useAddGroupToWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -83,7 +83,7 @@ export const useLeaveProject = () => {
|
|||||||
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`);
|
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
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`);
|
return apiRequest.post(`/api/v1/workspace/${workspaceId}/migrate-v3`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
DeleteWorkspaceDTO,
|
DeleteWorkspaceDTO,
|
||||||
NameWorkspaceSecretsDTO,
|
NameWorkspaceSecretsDTO,
|
||||||
ProjectIdentityOrderBy,
|
ProjectIdentityOrderBy,
|
||||||
|
ProjectType,
|
||||||
TGetUpgradeProjectStatusDTO,
|
TGetUpgradeProjectStatusDTO,
|
||||||
TListProjectIdentitiesDTO,
|
TListProjectIdentitiesDTO,
|
||||||
ToggleAutoCapitalizationDTO,
|
ToggleAutoCapitalizationDTO,
|
||||||
@@ -82,7 +83,7 @@ export const useUpgradeProject = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
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", {
|
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>("/api/v1/workspace", {
|
||||||
params: {
|
params: {
|
||||||
includeRoles
|
includeRoles,
|
||||||
|
type
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data.workspaces;
|
return data.workspaces;
|
||||||
@@ -139,8 +141,16 @@ export const useGetWorkspaceById = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetUserWorkspaces = (includeRoles?: boolean) =>
|
export const useGetUserWorkspaces = ({
|
||||||
useQuery(workspaceKeys.getAllUserWorkspace, () => fetchUserWorkspaces(includeRoles));
|
includeRoles,
|
||||||
|
type
|
||||||
|
}: {
|
||||||
|
includeRoles?: boolean;
|
||||||
|
type?: ProjectType;
|
||||||
|
} = {}) =>
|
||||||
|
useQuery(workspaceKeys.getAllUserWorkspace(type || ""), () =>
|
||||||
|
fetchUserWorkspaces(includeRoles, type)
|
||||||
|
);
|
||||||
|
|
||||||
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
||||||
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
||||||
@@ -206,33 +216,26 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
|
|||||||
refetchInterval: 4000
|
refetchInterval: 4000
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createWorkspace = ({
|
export const createWorkspace = (
|
||||||
projectName,
|
dto: CreateWorkspaceDTO
|
||||||
projectDescription,
|
): Promise<{ data: { project: Workspace } }> => {
|
||||||
kmsKeyId,
|
return apiRequest.post("/api/v2/workspace", dto);
|
||||||
template
|
|
||||||
}: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => {
|
|
||||||
return apiRequest.post("/api/v2/workspace", {
|
|
||||||
projectName,
|
|
||||||
projectDescription,
|
|
||||||
kmsKeyId,
|
|
||||||
template
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreateWorkspace = () => {
|
export const useCreateWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
|
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
|
||||||
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
|
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
|
||||||
createWorkspace({
|
createWorkspace({
|
||||||
projectName,
|
projectName,
|
||||||
projectDescription,
|
projectDescription,
|
||||||
kmsKeyId,
|
kmsKeyId,
|
||||||
template
|
template,
|
||||||
|
type
|
||||||
}),
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.data.project.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -240,15 +243,19 @@ export const useCreateWorkspace = () => {
|
|||||||
export const useUpdateProject = () => {
|
export const useUpdateProject = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateProjectDTO>({
|
return useMutation<Workspace, {}, UpdateProjectDTO>({
|
||||||
mutationFn: ({ projectID, newProjectName, newProjectDescription }) => {
|
mutationFn: async ({ projectID, newProjectName, newProjectDescription }) => {
|
||||||
return apiRequest.patch(`/api/v1/workspace/${projectID}`, {
|
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||||
name: newProjectName,
|
`/api/v1/workspace/${projectID}`,
|
||||||
description: newProjectDescription
|
{
|
||||||
});
|
name: newProjectName,
|
||||||
|
description: newProjectDescription
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -256,13 +263,18 @@ export const useUpdateProject = () => {
|
|||||||
export const useToggleAutoCapitalization = () => {
|
export const useToggleAutoCapitalization = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({
|
return useMutation<Workspace, {}, ToggleAutoCapitalizationDTO>({
|
||||||
mutationFn: ({ workspaceID, state }) =>
|
mutationFn: async ({ workspaceID, state }) => {
|
||||||
apiRequest.post(`/api/v1/workspace/${workspaceID}/auto-capitalization`, {
|
const { data } = await apiRequest.post<{ workspace: Workspace }>(
|
||||||
autoCapitalization: state
|
`/api/v1/workspace/${workspaceID}/auto-capitalization`,
|
||||||
}),
|
{
|
||||||
onSuccess: () => {
|
autoCapitalization: state
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
}
|
||||||
|
);
|
||||||
|
return data.workspace;
|
||||||
|
},
|
||||||
|
onSuccess: (dto) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -270,14 +282,15 @@ export const useToggleAutoCapitalization = () => {
|
|||||||
export const useUpdateWorkspaceVersionLimit = () => {
|
export const useUpdateWorkspaceVersionLimit = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
|
return useMutation<Workspace, {}, UpdatePitVersionLimitDTO>({
|
||||||
mutationFn: ({ projectSlug, pitVersionLimit }) => {
|
mutationFn: async ({ projectSlug, pitVersionLimit }) => {
|
||||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
const { data } = await apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
||||||
pitVersionLimit
|
pitVersionLimit
|
||||||
});
|
});
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -285,14 +298,18 @@ export const useUpdateWorkspaceVersionLimit = () => {
|
|||||||
export const useUpdateWorkspaceAuditLogsRetention = () => {
|
export const useUpdateWorkspaceAuditLogsRetention = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateAuditLogsRetentionDTO>({
|
return useMutation<Workspace, {}, UpdateAuditLogsRetentionDTO>({
|
||||||
mutationFn: ({ projectSlug, auditLogsRetentionDays }) => {
|
mutationFn: async ({ projectSlug, auditLogsRetentionDays }) => {
|
||||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/audit-logs-retention`, {
|
const { data } = await apiRequest.put(
|
||||||
auditLogsRetentionDays
|
`/api/v1/workspace/${projectSlug}/audit-logs-retention`,
|
||||||
});
|
{
|
||||||
|
auditLogsRetentionDays
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -300,12 +317,13 @@ export const useUpdateWorkspaceAuditLogsRetention = () => {
|
|||||||
export const useDeleteWorkspace = () => {
|
export const useDeleteWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeleteWorkspaceDTO>({
|
return useMutation<Workspace, {}, DeleteWorkspaceDTO>({
|
||||||
mutationFn: ({ workspaceID }) => {
|
mutationFn: async ({ workspaceID }) => {
|
||||||
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
const { data } = await apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
queryClient.invalidateQueries(["org-admin-projects"]);
|
queryClient.invalidateQueries(["org-admin-projects"]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -322,7 +340,7 @@ export const useCreateWsEnvironment = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -339,7 +357,7 @@ export const useUpdateWsEnvironment = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
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}`);
|
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/environments/${id}`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export const workspaceKeys = {
|
|||||||
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
|
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
|
||||||
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
|
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
|
||||||
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
||||||
getAllUserWorkspace: ["workspaces"] as const,
|
getAllUserWorkspace: (type?: string) =>
|
||||||
|
type ? ["workspaces", { type }] : (["workspace"] as const),
|
||||||
getWorkspaceAuditLogs: (workspaceId: string) =>
|
getWorkspaceAuditLogs: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-audit-logs"] as const,
|
[{ workspaceId }, "workspace-audit-logs"] as const,
|
||||||
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ export enum ProjectVersion {
|
|||||||
V3 = 3
|
V3 = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectType {
|
||||||
|
SecretManager = "secret-manager",
|
||||||
|
CertificateManager = "cert-manager",
|
||||||
|
Cmek = "cmek"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectUserMembershipTemporaryMode {
|
export enum ProjectUserMembershipTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
@@ -16,6 +22,7 @@ export type Workspace = {
|
|||||||
__v: number;
|
__v: number;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: ProjectType;
|
||||||
description?: string;
|
description?: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
version: ProjectVersion;
|
version: ProjectVersion;
|
||||||
@@ -59,6 +66,7 @@ export type CreateWorkspaceDTO = {
|
|||||||
projectDescription?: string;
|
projectDescription?: string;
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
template?: string;
|
template?: string;
|
||||||
|
type: ProjectType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateProjectDTO = {
|
export type UpdateProjectDTO = {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
!router.asPath.includes("secret-scanning") &&
|
!router.asPath.includes("secret-scanning") &&
|
||||||
!router.asPath.includes("integration")))
|
!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")) {
|
// 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">
|
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
||||||
{(router.asPath.includes("project") ||
|
{(router.asPath.includes("project") ||
|
||||||
router.asPath.includes("integrations")) && (
|
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">
|
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
</div>
|
</div>
|
||||||
@@ -379,7 +379,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
(!router.asPath.includes("personal") && currentWorkspace ? (
|
(!router.asPath.includes("personal") && currentWorkspace ? (
|
||||||
<ProjectSelect />
|
<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">
|
<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" />
|
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
|
||||||
Back to organization
|
Back to organization
|
||||||
@@ -493,13 +493,33 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
) : (
|
) : (
|
||||||
<Menu className="mt-4">
|
<Menu className="mt-4">
|
||||||
<Link href={`/org/${currentOrg?.id}/overview`} passHref>
|
<Link href={`/org/${currentOrg?.id}/secret-manager/overview`} passHref>
|
||||||
<a>
|
<a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isSelected={router.asPath.includes("/overview")}
|
isSelected={router.asPath.includes("/secret-manager/overview")}
|
||||||
icon="system-outline-165-view-carousel"
|
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>
|
</MenuItem>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -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:[email protected]"
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
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 →</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||||
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
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 };
|
type TWorkspaceWithFaveProp = Workspace & { isFavorite: boolean };
|
||||||
|
|
||||||
@@ -138,6 +138,7 @@ export const ProjectSelect = () => {
|
|||||||
|
|
||||||
const { options, value } = useMemo(() => {
|
const { options, value } = useMemo(() => {
|
||||||
const projectOptions = workspaces
|
const projectOptions = workspaces
|
||||||
|
.filter((el) => el.type === currentWorkspace?.type)
|
||||||
.map((w): Workspace & { isFavorite: boolean } => ({
|
.map((w): Workspace & { isFavorite: boolean } => ({
|
||||||
...w,
|
...w,
|
||||||
isFavorite: Boolean(projectFavorites?.includes(w.id))
|
isFavorite: Boolean(projectFavorites?.includes(w.id))
|
||||||
@@ -206,6 +207,7 @@ export const ProjectSelect = () => {
|
|||||||
<NewProjectModal
|
<NewProjectModal
|
||||||
isOpen={popUp.addNewWs.isOpen}
|
isOpen={popUp.addNewWs.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
|
||||||
|
projectType={currentWorkspace?.type || ProjectType.SecretManager}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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
+3
-1
@@ -71,7 +71,9 @@ export const AddOrgMemberModal = ({
|
|||||||
const { data: organizationRoles } = useGetOrgRoles(currentOrg?.id ?? "");
|
const { data: organizationRoles } = useGetOrgRoles(currentOrg?.id ?? "");
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg();
|
const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg();
|
||||||
const { data: projects, isLoading: isProjectsLoading } = useGetUserWorkspaces(true);
|
const { data: projects, isLoading: isProjectsLoading } = useGetUserWorkspaces({
|
||||||
|
includeRoles: true
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ import { useProjectPermission, useWorkspace } from "@app/context";
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useGetWorkspaceById, useMigrateProjectToV3, workspaceKeys } from "@app/hooks/api";
|
import { useGetWorkspaceById, useMigrateProjectToV3, workspaceKeys } from "@app/hooks/api";
|
||||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
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 {
|
enum ProjectUpgradeStatus {
|
||||||
InProgress = "IN_PROGRESS",
|
InProgress = "IN_PROGRESS",
|
||||||
@@ -53,7 +53,7 @@ export const SecretV2MigrationSection = () => {
|
|||||||
if (isProjectUpgraded && migrateProjectToV3.data) {
|
if (isProjectUpgraded && migrateProjectToV3.data) {
|
||||||
createNotification({ type: "success", text: "Project upgrade completed successfully" });
|
createNotification({ type: "success", text: "Project upgrade completed successfully" });
|
||||||
migrateProjectToV3.reset();
|
migrateProjectToV3.reset();
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
}, [isProjectUpgraded, Boolean(migrateProjectToV3.data)]);
|
}, [isProjectUpgraded, Boolean(migrateProjectToV3.data)]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user