mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
48 Commits
doc/add-na
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
c90ecd336c | |||
d8b1da3ddd | |||
58e86382fe | |||
2080c4419e | |||
b582a4a06d | |||
a5c6a864de | |||
5082c1ba3b | |||
cceb08b1b5 | |||
4c34e58945 | |||
72de1901a1 | |||
65fefcdd87 | |||
8e753eda72 | |||
7137c94fa2 | |||
52ea7dfa61 | |||
093925ed0e | |||
4491f2d8f1 | |||
4a401957c7 | |||
0c673f6cca | |||
10f4cbf11f | |||
a6a8c32326 | |||
99a474dba7 | |||
e439f4e5aa | |||
ae2ecf1540 | |||
10214ea5dc | |||
918cd414a8 | |||
f9a125acee | |||
52415ea83e | |||
c5ca2b6796 | |||
ef5bcac925 | |||
6cbeb29b4e | |||
fbe344c0df | |||
5821f65a63 | |||
3af510d487 | |||
c15adc7df9 | |||
93af7573ac | |||
cddda1148e | |||
9c37eeeda6 | |||
eadf5bef77 | |||
5dff46ee3a | |||
8b202c2a79 | |||
4574519a76 | |||
82ee77bc05 | |||
9a861499df | |||
d1f3c98f21 | |||
c501c85eb8 | |||
ab7983973e | |||
9832915eba | |||
b98c8629e5 |
@ -475,10 +475,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
}),
|
||||
z.object({
|
||||
op: z.literal("add"),
|
||||
value: z.object({
|
||||
value: z.string().trim(),
|
||||
display: z.string().trim().optional()
|
||||
})
|
||||
path: z.string().trim(),
|
||||
value: z.array(
|
||||
z.object({
|
||||
value: z.string().trim(),
|
||||
display: z.string().trim().optional()
|
||||
})
|
||||
)
|
||||
})
|
||||
])
|
||||
)
|
||||
|
@ -38,7 +38,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
has_used_trial: true,
|
||||
secretApproval: false,
|
||||
secretRotation: true,
|
||||
caCrl: false
|
||||
caCrl: false,
|
||||
instanceUserManagement: false
|
||||
});
|
||||
|
||||
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||
|
@ -218,6 +218,8 @@ export const licenseServiceFactory = ({
|
||||
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
|
||||
const usedSeats = await licenseDAL.countOfOrgMembers(null, tx);
|
||||
const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx);
|
||||
onPremFeatures.membersUsed = usedSeats;
|
||||
onPremFeatures.identitiesUsed = usedIdentitySeats;
|
||||
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, {
|
||||
usedSeats,
|
||||
usedIdentitySeats
|
||||
|
@ -30,9 +30,9 @@ export type TFeatureSet = {
|
||||
workspacesUsed: 0;
|
||||
dynamicSecret: false;
|
||||
memberLimit: null;
|
||||
membersUsed: 0;
|
||||
membersUsed: number;
|
||||
identityLimit: null;
|
||||
identitiesUsed: 0;
|
||||
identitiesUsed: number;
|
||||
environmentLimit: null;
|
||||
environmentsUsed: 0;
|
||||
secretVersioning: true;
|
||||
@ -56,6 +56,7 @@ export type TFeatureSet = {
|
||||
secretApproval: false;
|
||||
secretRotation: true;
|
||||
caCrl: false;
|
||||
instanceUserManagement: false;
|
||||
};
|
||||
|
||||
export type TOrgPlansTableDTO = {
|
||||
|
@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
|
||||
import { OrgMembershipRole, OrgMembershipStatus, TableName, TOrgMemberships, TUsers } from "@app/db/schemas";
|
||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
@ -66,7 +66,7 @@ type TScimServiceFactoryDep = {
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
||||
groupDAL: Pick<
|
||||
TGroupDALFactory,
|
||||
"create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups" | "transaction"
|
||||
"create" | "findOne" | "findAllGroupMembers" | "delete" | "findGroups" | "transaction" | "updateById" | "update"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
userGroupMembershipDAL: Pick<
|
||||
@ -817,7 +817,6 @@ export const scimServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: add support for add/remove op
|
||||
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.groups)
|
||||
@ -840,23 +839,45 @@ export const scimServiceFactory = ({
|
||||
status: 403
|
||||
});
|
||||
|
||||
let group: TGroups | undefined;
|
||||
let group = await groupDAL.findOne({
|
||||
id: groupId,
|
||||
orgId
|
||||
});
|
||||
|
||||
if (!group) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Group Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
for await (const operation of operations) {
|
||||
switch (operation.op) {
|
||||
case "replace": {
|
||||
await groupDAL.update(
|
||||
{
|
||||
id: groupId,
|
||||
orgId
|
||||
},
|
||||
{
|
||||
name: operation.value.displayName
|
||||
}
|
||||
);
|
||||
group = await groupDAL.updateById(group.id, {
|
||||
name: operation.value.displayName
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "add": {
|
||||
// TODO
|
||||
const orgMemberships = await orgMembershipDAL.find({
|
||||
$in: {
|
||||
id: operation.value.map((member) => member.value)
|
||||
}
|
||||
});
|
||||
|
||||
await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "remove": {
|
||||
@ -872,13 +893,6 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (!group) {
|
||||
throw new ScimRequestError({
|
||||
detail: "Group Not Found",
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
return buildScimGroup({
|
||||
groupId: group.id,
|
||||
name: group.name,
|
||||
|
@ -125,10 +125,11 @@ type TRemoveOp = {
|
||||
|
||||
type TAddOp = {
|
||||
op: "add";
|
||||
path: string;
|
||||
value: {
|
||||
value: string;
|
||||
display?: string;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
export type TDeleteScimGroupDTO = {
|
||||
|
@ -469,7 +469,8 @@ export const registerRoutes = async (
|
||||
authService: loginService,
|
||||
serverCfgDAL: superAdminDAL,
|
||||
orgService,
|
||||
keyStore
|
||||
keyStore,
|
||||
licenseService
|
||||
});
|
||||
const rateLimitService = rateLimitServiceFactory({
|
||||
rateLimitDAL,
|
||||
|
@ -100,6 +100,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -108,6 +109,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -182,11 +184,12 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(AWS_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
|
@ -90,6 +90,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -98,6 +99,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -173,11 +175,12 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(AZURE_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
|
@ -91,6 +91,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -99,6 +100,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -175,11 +177,12 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(GCP_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
|
@ -106,6 +106,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -114,6 +115,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -196,7 +198,13 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(315360000)
|
||||
.optional()
|
||||
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
@ -206,6 +214,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
|
@ -106,6 +106,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -114,6 +115,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -201,6 +203,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -209,6 +212,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
|
@ -39,6 +39,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -47,6 +48,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -117,11 +119,12 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(TOKEN_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
|
@ -107,6 +107,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
@ -115,6 +116,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -196,7 +198,13 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(315360000)
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
@ -206,6 +214,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
@ -362,7 +371,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
body: z.object({
|
||||
description: z.string().trim().default("").describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.description),
|
||||
numUsesLimit: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.numUsesLimit),
|
||||
ttl: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
|
||||
ttl: z.number().min(0).max(315360000).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -57,6 +57,12 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
`${TableName.IdentityOidcAuth}.identityId`
|
||||
);
|
||||
})
|
||||
.leftJoin(TableName.IdentityTokenAuth, (qb) => {
|
||||
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.TOKEN_AUTH])).andOn(
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityTokenAuth}.identityId`
|
||||
);
|
||||
})
|
||||
.select(selectAllTableCols(TableName.IdentityAccessToken))
|
||||
.select(
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
|
||||
@ -65,6 +71,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
|
||||
db.ref("name").withSchema(TableName.Identity)
|
||||
)
|
||||
.first();
|
||||
@ -79,7 +86,8 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
doc.accessTokenTrustedIpsAws ||
|
||||
doc.accessTokenTrustedIpsAzure ||
|
||||
doc.accessTokenTrustedIpsK8s ||
|
||||
doc.accessTokenTrustedIpsOidc
|
||||
doc.accessTokenTrustedIpsOidc ||
|
||||
doc.accessTokenTrustedIpsToken
|
||||
};
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { TableName, TSecretSharing } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TSecretSharingDALFactory = ReturnType<typeof secretSharingDALFactory>;
|
||||
|
||||
@ -13,15 +13,58 @@ export const secretSharingDALFactory = (db: TDbClient) => {
|
||||
const pruneExpiredSharedSecrets = async (tx?: Knex) => {
|
||||
try {
|
||||
const today = new Date();
|
||||
const docs = await (tx || db)(TableName.SecretSharing).where("expiresAt", "<", today).del();
|
||||
const docs = await (tx || db)(TableName.SecretSharing)
|
||||
.where("expiresAt", "<", today)
|
||||
.andWhere("encryptedValue", "<>", "")
|
||||
.update({
|
||||
encryptedValue: "",
|
||||
tag: "",
|
||||
iv: "",
|
||||
hashedHex: ""
|
||||
});
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "pruneExpiredSharedSecrets" });
|
||||
}
|
||||
};
|
||||
|
||||
const findActiveSharedSecrets = async (filters: Partial<TSecretSharing>, tx?: Knex) => {
|
||||
try {
|
||||
const now = new Date();
|
||||
return await (tx || db)(TableName.SecretSharing)
|
||||
.where(filters)
|
||||
.andWhere("expiresAt", ">", now)
|
||||
.andWhere("encryptedValue", "<>", "")
|
||||
.select(selectAllTableCols(TableName.SecretSharing))
|
||||
.orderBy("expiresAt", "asc");
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
error,
|
||||
name: "Find Active Shared Secrets"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const softDeleteById = async (id: string) => {
|
||||
try {
|
||||
await sharedSecretOrm.updateById(id, {
|
||||
encryptedValue: "",
|
||||
iv: "",
|
||||
tag: "",
|
||||
hashedHex: ""
|
||||
});
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
error,
|
||||
name: "Soft Delete Shared Secret"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...sharedSecretOrm,
|
||||
pruneExpiredSharedSecrets
|
||||
pruneExpiredSharedSecrets,
|
||||
softDeleteById,
|
||||
findActiveSharedSecrets
|
||||
};
|
||||
};
|
||||
|
@ -101,7 +101,7 @@ export const secretSharingServiceFactory = ({
|
||||
const { actor, actorId, orgId, actorAuthMethod, actorOrgId } = getSharedSecretsInput;
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
if (!permission) throw new UnauthorizedError({ name: "User not in org" });
|
||||
const userSharedSecrets = await secretSharingDAL.find({ userId: actorId, orgId }, { sort: [["expiresAt", "asc"]] });
|
||||
const userSharedSecrets = await secretSharingDAL.findActiveSharedSecrets({ userId: actorId, orgId });
|
||||
return userSharedSecrets;
|
||||
};
|
||||
|
||||
@ -113,7 +113,7 @@ export const secretSharingServiceFactory = ({
|
||||
}
|
||||
if (sharedSecret.expiresAfterViews != null && sharedSecret.expiresAfterViews >= 0) {
|
||||
if (sharedSecret.expiresAfterViews === 0) {
|
||||
await secretSharingDAL.deleteById(sharedSecretId);
|
||||
await secretSharingDAL.softDeleteById(sharedSecretId);
|
||||
return;
|
||||
}
|
||||
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
|
||||
|
@ -1,6 +1,7 @@
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
@ -20,6 +21,7 @@ type TSuperAdminServiceFactoryDep = {
|
||||
authService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
||||
orgService: Pick<TOrgServiceFactory, "createOrganization">;
|
||||
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
|
||||
};
|
||||
|
||||
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
||||
@ -36,7 +38,8 @@ export const superAdminServiceFactory = ({
|
||||
userDAL,
|
||||
authService,
|
||||
orgService,
|
||||
keyStore
|
||||
keyStore,
|
||||
licenseService
|
||||
}: TSuperAdminServiceFactoryDep) => {
|
||||
const initServerCfg = async () => {
|
||||
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
||||
@ -219,6 +222,12 @@ export const superAdminServiceFactory = ({
|
||||
};
|
||||
|
||||
const deleteUser = async (userId: string) => {
|
||||
if (!licenseService.onPremFeatures?.instanceUserManagement) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to delete user due to plan restriction. Upgrade to Infisical's Pro plan."
|
||||
});
|
||||
}
|
||||
|
||||
const user = await userDAL.deleteById(userId);
|
||||
return user;
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ require (
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
|
||||
github.com/rs/cors v1.9.0
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/rs/zerolog v1.26.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
@ -52,7 +52,7 @@ require (
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/danieljoos/wincred v1.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
|
@ -117,8 +117,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -356,8 +356,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
|
||||
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: OIDC Auth
|
||||
title: General
|
||||
description: "Learn how to authenticate with Infisical from any platform or environment using OpenID Connect (OIDC)."
|
||||
---
|
||||
|
||||
@ -7,7 +7,7 @@ description: "Learn how to authenticate with Infisical from any platform or envi
|
||||
|
||||
## Diagram
|
||||
|
||||
The following sequence digram illustrates the OIDC Auth workflow for authenticating clients with Infisical.
|
||||
The following sequence diagram illustrates the OIDC Auth workflow for authenticating clients with Infisical.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@ -83,7 +83,7 @@ In the following steps, we explore how to create and use identities to access th
|
||||
<Tip>Restrict access by configuring the Subject, Audiences, and Claims fields</Tip>
|
||||
|
||||
Here's some more guidance on each field:
|
||||
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration information from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT.
|
||||
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT.
|
||||
- Issuer: The unique identifier of the identity provider issuing the JWT. This value is used to verify the iss (issuer) claim in the JWT to ensure the token is issued by a trusted provider.
|
||||
- CA Certificate: The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints.
|
||||
- Subject: The expected principal that is the subject of the JWT. The `sub` (subject) claim in the JWT should match this value.
|
170
docs/documentation/platform/identities/oidc-auth/github.mdx
Normal file
170
docs/documentation/platform/identities/oidc-auth/github.mdx
Normal file
@ -0,0 +1,170 @@
|
||||
---
|
||||
title: Github
|
||||
description: "Learn how to authenticate Github workflows with Infisical using OpenID Connect (OIDC)."
|
||||
---
|
||||
|
||||
**OIDC Auth** is a platform-agnostic JWT-based authentication method that can be used to authenticate from any platform or environment using an identity provider with OpenID Connect.
|
||||
|
||||
## Diagram
|
||||
|
||||
The following sequence diagram illustrates the OIDC Auth workflow for authenticating Github workflows with Infisical.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Github Workflow
|
||||
participant Idp as Identity Provider
|
||||
participant Infis as Infisical
|
||||
|
||||
Client->>Idp: Step 1: Request identity token
|
||||
Idp-->>Client: Return JWT with verifiable claims
|
||||
|
||||
Note over Client,Infis: Step 2: Login Operation
|
||||
Client->>Infis: Send signed JWT to /api/v1/auth/oidc-auth/login
|
||||
|
||||
Note over Infis,Idp: Step 3: Query verification
|
||||
Infis->>Idp: Request JWT public key using OIDC Discovery
|
||||
Idp-->>Infis: Return public key
|
||||
|
||||
Note over Infis: Step 4: JWT validation
|
||||
Infis->>Client: Return short-lived access token
|
||||
|
||||
Note over Client,Infis: Step 5: Access Infisical API with Token
|
||||
Client->>Infis: Make authenticated requests using the short-lived access token
|
||||
```
|
||||
|
||||
## Concept
|
||||
|
||||
At a high-level, Infisical authenticates a client by verifying the JWT and checking that it meets specific requirements (e.g. it is issued by a trusted identity provider) at the `/api/v1/auth/oidc-auth/login` endpoint. If successful,
|
||||
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
|
||||
|
||||
To be more specific:
|
||||
|
||||
1. The Github workflow requests an identity token from Github's identity provider.
|
||||
2. The fetched identity token is sent to Infisical at the `/api/v1/auth/oidc-auth/login` endpoint.
|
||||
3. Infisical fetches the public key that was used to sign the identity token from Github's identity provider using OIDC Discovery.
|
||||
4. Infisical validates the JWT using the public key provided by the identity provider and checks that the subject, audience, and claims of the token matches with the set criteria.
|
||||
5. If all is well, Infisical returns a short-lived access token that the Github workflow can use to make authenticated requests to the Infisical API.
|
||||
|
||||
<Note>
|
||||
Infisical needs network-level access to Github's identity provider endpoints.
|
||||
</Note>
|
||||
|
||||
## Guide
|
||||
|
||||
In the following steps, we explore how to create and use identities to access the Infisical API using the OIDC Auth authentication method.
|
||||
|
||||
<Steps>
|
||||
<Step title="Creating an identity">
|
||||
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
|
||||
|
||||

|
||||
|
||||
When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
|
||||
|
||||

|
||||
|
||||
Now input a few details for your new identity. Here's some guidance for each field:
|
||||
|
||||
- Name (required): A friendly name for the identity.
|
||||
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
|
||||
|
||||
Once you've created an identity, you'll be redirected to a page where you can manage the identity.
|
||||
|
||||

|
||||
|
||||
Since the identity has been configured with Universal Auth by default, you should re-configure it to use OIDC Auth instead. To do this, press to edit the **Authentication** section,
|
||||
remove the existing Universal Auth configuration, and add a new OIDC Auth configuration onto the identity.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<Warning>Restrict access by configuring the Subject, Audiences, and Claims fields</Warning>
|
||||
|
||||
Here's some more guidance on each field:
|
||||
- OIDC Discovery URL: The URL used to retrieve the OpenID Connect configuration from the identity provider. This will be used to fetch the public key needed for verifying the provided JWT. This should be set to `https://token.actions.githubusercontent.com`
|
||||
- Issuer: The unique identifier of the identity provider issuing the JWT. This value is used to verify the iss (issuer) claim in the JWT to ensure the token is issued by a trusted provider. This should be set to `https://token.actions.githubusercontent.com`
|
||||
- CA Certificate: The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints. For Github workflows, this can be left as blank.
|
||||
- Subject: The expected principal that is the subject of the JWT. The format of the sub field for GitHub workflow OIDC tokens is as follows: `"repo:<owner>/<repo>:<environment>"`. The environment can be where the GitHub workflow is running, such as `environment`, `ref`, or `job_workflow_ref`. For example, if you have a repository owned by octocat named example-repo, and the GitHub workflow is running on the main branch, the subject field might look like this: `repo:octocat/example-repo:ref:refs/heads/main`
|
||||
- Audiences: A list of intended recipients. This value is checked against the aud (audience) claim in the token. By default, set this to the URL of the repository owner, such as the organization that owns the repository (e.g. `https://github.com/octo-org`).
|
||||
- Claims: Additional information or attributes that should be present in the JWT for it to be valid. You can refer to Github's [documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token) for the complete list of supported claims.
|
||||
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
|
||||
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
|
||||
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
|
||||
- Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
|
||||
|
||||
<Tip>If you are unsure about what to configure for the subject, audience, and claims fields you can use [github/actions-oidc-debugger](https://github.com/github/actions-oidc-debugger) to get the appropriate values. Alternatively, you can fetch the JWT from the workflow and inspect the fields manually.</Tip>
|
||||
</Step>
|
||||
<Step title="Adding an identity to a project">
|
||||
To enable the identity to access project-level resources such as secrets within a specific project, you should add it to that project.
|
||||
|
||||
To do this, head over to the project you want to add the identity to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
|
||||
|
||||
Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
|
||||
|
||||

|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Accessing the Infisical API with the identity">
|
||||
As a prerequisite, you will need to set `id-token:write` permissions for the Github workflow. This setting allows the JWT to be requested from Github's OIDC provider.
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
...
|
||||
```
|
||||
|
||||
To access the Infisical API as the identity, you need to fetch an identity token from Github's identity provider and make a request to the `/api/v1/auth/oidc-auth/login` endpoint in exchange for an access token.
|
||||
The identity token can be fetched using either of the following approaches:
|
||||
- Using environment variables on the runner (`ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`).
|
||||
```yaml
|
||||
steps:
|
||||
- name: Request OIDC Token
|
||||
run: |
|
||||
echo "Requesting OIDC token..."
|
||||
TOKEN=$(curl -s -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value')
|
||||
echo "TOKEN=$TOKEN" >> $GITHUB_ENV
|
||||
```
|
||||
|
||||
- Using `getIDToken()` from the Github Actions toolkit.
|
||||
|
||||
Below is an example of how a Github workflow can be configured to fetch secrets from Infisical using the [Infisical Secrets Action](https://github.com/Infisical/secrets-action) with OIDC Auth.
|
||||
```yaml
|
||||
name: Manual workflow
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Infisical/secrets-action@v1.0.7
|
||||
with:
|
||||
method: "oidc"
|
||||
env-slug: "dev"
|
||||
project-slug: "ggggg-9-des"
|
||||
identity-id: "6b579c00-5c85-4b44-aabe-f8a
|
||||
...
|
||||
```
|
||||
|
||||
Preceding steps can then use the secret values injected onto the workflow's environment.
|
||||
|
||||
<Tip>
|
||||
We recommend using [Infisical Secrets Action](https://github.com/Infisical/secrets-action) to authenticate with Infisical using OIDC Auth as it handles the authentication process including the fetching of identity tokens for you.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
Each identity access token has a time-to-live (TLL) which you can infer from the response of the login operation;
|
||||
the default TTL is `7200` seconds which can be adjusted.
|
||||
|
||||
If an identity access token expires, it can no longer authenticate with the Infisical API. In this case,
|
||||
a new access token should be obtained by performing another login operation.
|
||||
</Note>
|
||||
</Step>
|
||||
|
||||
</Steps>
|
@ -168,7 +168,13 @@
|
||||
"documentation/platform/identities/gcp-auth",
|
||||
"documentation/platform/identities/azure-auth",
|
||||
"documentation/platform/identities/aws-auth",
|
||||
"documentation/platform/identities/oidc-auth",
|
||||
{
|
||||
"group": "OIDC Auth",
|
||||
"pages": [
|
||||
"documentation/platform/identities/oidc-auth/general",
|
||||
"documentation/platform/identities/oidc-auth/github"
|
||||
]
|
||||
},
|
||||
"documentation/platform/mfa",
|
||||
{
|
||||
"group": "SSO",
|
||||
|
@ -2,7 +2,7 @@ const path = require("path");
|
||||
|
||||
const ContentSecurityPolicy = `
|
||||
default-src 'self';
|
||||
script-src 'self' https://app.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval';
|
||||
script-src 'self' https://*.posthog.com https://*.*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval';
|
||||
style-src 'self' https://rsms.me 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
|
||||
child-src https://api.stripe.com;
|
||||
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/ https://hcaptcha.com https://*.hcaptcha.com;
|
||||
|
483
frontend/package-lock.json
generated
483
frontend/package-lock.json
generated
@ -47,7 +47,7 @@
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"add": "^2.0.6",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"axios": "^0.27.2",
|
||||
"axios": "^0.28.0",
|
||||
"axios-auth-refresh": "^3.3.6",
|
||||
"base64-loader": "^1.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
@ -139,7 +139,7 @@
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||
"storybook": "^7.5.2",
|
||||
"storybook": "^7.6.20",
|
||||
"storybook-dark-mode": "^3.0.0",
|
||||
"tailwindcss": "3.2",
|
||||
"typescript": "^4.9.3"
|
||||
@ -6200,15 +6200,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.19.tgz",
|
||||
"integrity": "sha512-Dt5OLh97xeWh4h2mk9uG0SbCxBKHPhIiHLHAKEIDzIZBdwUhuyncVNDPHW2NlXM+S7U0/iKs2tw05waqh2lHvg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.20.tgz",
|
||||
"integrity": "sha512-e2GzpjLaw6CM/XSmc4qJRzBF8GOoOyotyu3JrSPTYOt4RD8kjUsK4QlismQM1DQRu8i39aIexxmRbiJyD74xzQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
||||
"@storybook/core-common": "7.6.19",
|
||||
"@storybook/manager": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/core-common": "7.6.20",
|
||||
"@storybook/manager": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@types/ejs": "^3.1.1",
|
||||
"@types/find-cache-dir": "^3.2.1",
|
||||
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10",
|
||||
@ -6228,13 +6228,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager/node_modules/@storybook/channels": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
|
||||
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
|
||||
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"qs": "^6.10.0",
|
||||
"telejson": "^7.2.0",
|
||||
@ -6246,9 +6246,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager/node_modules/@storybook/client-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0"
|
||||
@ -6259,14 +6259,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager/node_modules/@storybook/core-common": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
|
||||
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
|
||||
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/find-cache-dir": "^3.2.1",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
@ -6294,9 +6294,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager/node_modules/@storybook/core-events": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
|
||||
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
|
||||
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -6307,9 +6307,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager/node_modules/@storybook/node-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -6317,12 +6317,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/builder-manager/node_modules/@storybook/types": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
|
||||
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
|
||||
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/express": "^4.7.0",
|
||||
"file-system-cache": "2.3.0"
|
||||
@ -6438,23 +6438,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.19.tgz",
|
||||
"integrity": "sha512-7OVy7nPgkLfgivv6/dmvoyU6pKl9EzWFk+g9izyQHiM/jS8jOiEyn6akG8Ebj6k5pWslo5lgiXUSW+cEEZUnqQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.20.tgz",
|
||||
"integrity": "sha512-ZlP+BJyqg7HlnXf7ypjG2CKMI/KVOn03jFIiClItE/jQfgR6kRFgtjRU7uajh427HHfjv9DRiur8nBzuO7vapA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@ndelangen/get-tarball": "^3.0.7",
|
||||
"@storybook/codemod": "7.6.19",
|
||||
"@storybook/core-common": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/core-server": "7.6.19",
|
||||
"@storybook/csf-tools": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/telemetry": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/codemod": "7.6.20",
|
||||
"@storybook/core-common": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/core-server": "7.6.20",
|
||||
"@storybook/csf-tools": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/telemetry": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/semver": "^7.3.4",
|
||||
"@yarnpkg/fslib": "2.10.3",
|
||||
"@yarnpkg/libzip": "2.3.0",
|
||||
@ -6494,13 +6494,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/channels": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
|
||||
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
|
||||
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"qs": "^6.10.0",
|
||||
"telejson": "^7.2.0",
|
||||
@ -6512,9 +6512,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/client-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0"
|
||||
@ -6525,14 +6525,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/core-common": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
|
||||
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
|
||||
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/find-cache-dir": "^3.2.1",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
@ -6560,9 +6560,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/core-events": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
|
||||
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
|
||||
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -6573,9 +6573,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/csf-tools": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
|
||||
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
|
||||
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.23.0",
|
||||
@ -6583,7 +6583,7 @@
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/types": "7.6.20",
|
||||
"fs-extra": "^11.1.0",
|
||||
"recast": "^0.23.1",
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -6594,9 +6594,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/node-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -6604,12 +6604,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/cli/node_modules/@storybook/types": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
|
||||
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
|
||||
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/express": "^4.7.0",
|
||||
"file-system-cache": "2.3.0"
|
||||
@ -6703,18 +6703,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.19.tgz",
|
||||
"integrity": "sha512-bmHE0iEEgWZ65dXCmasd+GreChjPiWkXu2FEa0cJmNz/PqY12GsXGls4ke1TkNTj4gdSZnbtJxbclPZZnib2tQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.20.tgz",
|
||||
"integrity": "sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/csf-tools": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/csf-tools": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/cross-spawn": "^6.0.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"globby": "^11.0.2",
|
||||
@ -6729,13 +6729,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod/node_modules/@storybook/channels": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
|
||||
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
|
||||
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"qs": "^6.10.0",
|
||||
"telejson": "^7.2.0",
|
||||
@ -6747,9 +6747,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod/node_modules/@storybook/client-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0"
|
||||
@ -6760,9 +6760,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod/node_modules/@storybook/core-events": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
|
||||
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
|
||||
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -6773,9 +6773,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod/node_modules/@storybook/csf-tools": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
|
||||
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
|
||||
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.23.0",
|
||||
@ -6783,7 +6783,7 @@
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/types": "7.6.20",
|
||||
"fs-extra": "^11.1.0",
|
||||
"recast": "^0.23.1",
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -6794,9 +6794,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod/node_modules/@storybook/node-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -6804,12 +6804,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/codemod/node_modules/@storybook/types": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
|
||||
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
|
||||
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/express": "^4.7.0",
|
||||
"file-system-cache": "2.3.0"
|
||||
@ -7063,26 +7063,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.19.tgz",
|
||||
"integrity": "sha512-7mKL73Wv5R2bEl0kJ6QJ9bOu5YY53Idu24QgvTnUdNsQazp2yUONBNwHIrNDnNEXm8SfCi4Mc9o0mmNRMIoiRA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.20.tgz",
|
||||
"integrity": "sha512-qC5BdbqqwMLTdCwMKZ1Hbc3+3AaxHYWLiJaXL9e8s8nJw89xV8c8l30QpbJOGvcDmsgY6UTtXYaJ96OsTr7MrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@aw-web-design/x-default-browser": "1.4.126",
|
||||
"@discoveryjs/json-ext": "^0.5.3",
|
||||
"@storybook/builder-manager": "7.6.19",
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/core-common": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/builder-manager": "7.6.20",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@storybook/core-common": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/csf-tools": "7.6.19",
|
||||
"@storybook/csf-tools": "7.6.20",
|
||||
"@storybook/docs-mdx": "^0.1.0",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/manager": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/preview-api": "7.6.19",
|
||||
"@storybook/telemetry": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/manager": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/preview-api": "7.6.20",
|
||||
"@storybook/telemetry": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/detect-port": "^1.3.0",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/pretty-hrtime": "^1.0.0",
|
||||
@ -7095,7 +7095,6 @@
|
||||
"express": "^4.17.3",
|
||||
"fs-extra": "^11.1.0",
|
||||
"globby": "^11.0.2",
|
||||
"ip": "^2.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"open": "^8.4.0",
|
||||
"pretty-hrtime": "^1.0.3",
|
||||
@ -7116,13 +7115,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/channels": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
|
||||
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
|
||||
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"qs": "^6.10.0",
|
||||
"telejson": "^7.2.0",
|
||||
@ -7134,9 +7133,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/client-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0"
|
||||
@ -7147,14 +7146,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/core-common": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
|
||||
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
|
||||
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/find-cache-dir": "^3.2.1",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
@ -7182,9 +7181,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/core-events": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
|
||||
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
|
||||
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -7195,9 +7194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/csf-tools": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
|
||||
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
|
||||
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.23.0",
|
||||
@ -7205,7 +7204,7 @@
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/types": "7.6.20",
|
||||
"fs-extra": "^11.1.0",
|
||||
"recast": "^0.23.1",
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -7216,9 +7215,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/node-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -7226,17 +7225,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/preview-api": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.19.tgz",
|
||||
"integrity": "sha512-04hdMSQucroJT4dBjQzRd7ZwH2hij8yx2nm5qd4HYGkd1ORkvlH6GOLph4XewNJl5Um3xfzFQzBhvkqvG0WaCQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.20.tgz",
|
||||
"integrity": "sha512-3ic2m9LDZEPwZk02wIhNc3n3rNvbi7VDKn52hDXfAxnL5EYm7yDICAkaWcVaTfblru2zn0EDJt7ROpthscTW5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/qs": "^6.9.5",
|
||||
"dequal": "^2.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
@ -7252,12 +7251,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/core-server/node_modules/@storybook/types": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
|
||||
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
|
||||
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/express": "^4.7.0",
|
||||
"file-system-cache": "2.3.0"
|
||||
@ -7372,9 +7371,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@storybook/manager": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.19.tgz",
|
||||
"integrity": "sha512-fZWQcf59x4P0iiBhrL74PZrqKJAPuk9sWjP8BIkGbf8wTZtUunbY5Sv4225fOL4NLJbuX9/RYLUPoxQ3nucGHA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.20.tgz",
|
||||
"integrity": "sha512-0Cf6WN0t7yEG2DR29tN5j+i7H/TH5EfPppg9h9/KiQSoFHk+6KLoy2p5do94acFU+Ro4+zzxvdCGbcYGKuArpg==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -7810,14 +7809,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.19.tgz",
|
||||
"integrity": "sha512-rA5xum4I36M57iiD3uzmW0MOdpl0vEpHWBSAa5hK0a0ALPeY9TgAsQlI/0dSyNYJ/K7aczEEN6d4qm1NC4u10A==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.20.tgz",
|
||||
"integrity": "sha512-dmAOCWmOscYN6aMbhCMmszQjoycg7tUPRVy2kTaWg6qX10wtMrvEtBV29W4eMvqdsoRj5kcvoNbzRdYcWBUOHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-common": "7.6.19",
|
||||
"@storybook/csf-tools": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-common": "7.6.20",
|
||||
"@storybook/csf-tools": "7.6.20",
|
||||
"chalk": "^4.1.0",
|
||||
"detect-package-manager": "^2.0.1",
|
||||
"fetch-retry": "^5.0.2",
|
||||
@ -7830,13 +7829,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/channels": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
|
||||
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
|
||||
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/client-logger": "7.6.19",
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/client-logger": "7.6.20",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"qs": "^6.10.0",
|
||||
"telejson": "^7.2.0",
|
||||
@ -7848,9 +7847,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/client-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0"
|
||||
@ -7861,14 +7860,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/core-common": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
|
||||
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
|
||||
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/core-events": "7.6.19",
|
||||
"@storybook/node-logger": "7.6.19",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/core-events": "7.6.20",
|
||||
"@storybook/node-logger": "7.6.20",
|
||||
"@storybook/types": "7.6.20",
|
||||
"@types/find-cache-dir": "^3.2.1",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
@ -7896,9 +7895,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/core-events": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
|
||||
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
|
||||
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -7909,9 +7908,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/csf-tools": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
|
||||
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
|
||||
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.23.0",
|
||||
@ -7919,7 +7918,7 @@
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@storybook/csf": "^0.1.2",
|
||||
"@storybook/types": "7.6.19",
|
||||
"@storybook/types": "7.6.20",
|
||||
"fs-extra": "^11.1.0",
|
||||
"recast": "^0.23.1",
|
||||
"ts-dedent": "^2.0.0"
|
||||
@ -7930,9 +7929,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/node-logger": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
|
||||
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
|
||||
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -7940,12 +7939,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/telemetry/node_modules/@storybook/types": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
|
||||
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
|
||||
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/channels": "7.6.19",
|
||||
"@storybook/channels": "7.6.20",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/express": "^4.7.0",
|
||||
"file-system-cache": "2.3.0"
|
||||
@ -10040,12 +10039,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz",
|
||||
"integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios-auth-refresh": {
|
||||
@ -10056,6 +10056,11 @@
|
||||
"axios": ">= 0.18 < 0.19.0 || >= 0.19.1"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
|
||||
@ -11451,6 +11456,12 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
|
||||
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/confusing-browser-globals": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
|
||||
@ -14389,9 +14400,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/flow-parser": {
|
||||
"version": "0.237.2",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.237.2.tgz",
|
||||
"integrity": "sha512-mvI/kdfr3l1waaPbThPA8dJa77nHXrfZIun+SWvFwSwDjmeByU7mGJGRmv1+7guU6ccyLV8e1lqZA1lD4iMGnQ==",
|
||||
"version": "0.239.1",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.239.1.tgz",
|
||||
"integrity": "sha512-topOrETNxJ6T2gAnQiWqAlzGPj8uI2wtmNOlDIMNB+qyvGJZ6R++STbUOTAYmvPhOMz2gXnXPH0hOvURYmrBow==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
@ -18147,6 +18158,30 @@
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz",
|
||||
"integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.3",
|
||||
"pathe": "^1.1.2",
|
||||
"pkg-types": "^1.1.1",
|
||||
"ufo": "^1.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
@ -18621,16 +18656,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz",
|
||||
"integrity": "sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==",
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz",
|
||||
"integrity": "sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.2.3",
|
||||
"execa": "^8.0.1",
|
||||
"pathe": "^1.1.2",
|
||||
"ufo": "^1.4.0"
|
||||
"pkg-types": "^1.1.1",
|
||||
"ufo": "^1.5.3"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
@ -19360,9 +19396,9 @@
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@ -19405,6 +19441,17 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz",
|
||||
"integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.7",
|
||||
"mlly": "^1.7.1",
|
||||
"pathe": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/pnp-webpack-plugin": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz",
|
||||
@ -19441,9 +19488,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||
"version": "8.4.39",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -19460,8 +19507,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"picocolors": "^1.0.1",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@ -20265,9 +20312,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer-core/node_modules/ws": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
||||
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
|
||||
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"async-limiter": "~1.0.0"
|
||||
@ -22254,9 +22301,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -22420,12 +22467,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/storybook": {
|
||||
"version": "7.6.19",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.19.tgz",
|
||||
"integrity": "sha512-xWD1C4vD/4KMffCrBBrUpsLUO/9uNpm8BVW8+Vcb30gkQDfficZ0oziWkmLexpT53VSioa24iazGXMwBqllYjQ==",
|
||||
"version": "7.6.20",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.20.tgz",
|
||||
"integrity": "sha512-Wt04pPTO71pwmRmsgkyZhNo4Bvdb/1pBAMsIFb9nQLykEdzzpXjvingxFFvdOG4nIowzwgxD+CLlyRqVJqnATw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@storybook/cli": "7.6.19"
|
||||
"@storybook/cli": "7.6.20"
|
||||
},
|
||||
"bin": {
|
||||
"sb": "index.js",
|
||||
@ -24661,9 +24708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
@ -55,7 +55,7 @@
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"add": "^2.0.6",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"axios": "^0.27.2",
|
||||
"axios": "^0.28.0",
|
||||
"axios-auth-refresh": "^3.3.6",
|
||||
"base64-loader": "^1.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
@ -147,7 +147,7 @@
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||
"storybook": "^7.5.2",
|
||||
"storybook": "^7.6.20",
|
||||
"storybook-dark-mode": "^3.0.0",
|
||||
"tailwindcss": "3.2",
|
||||
"typescript": "^4.9.3"
|
||||
|
@ -39,4 +39,5 @@ export type SubscriptionPlan = {
|
||||
trial_end: number | null;
|
||||
has_used_trial: boolean;
|
||||
caCrl: boolean;
|
||||
instanceUserManagement: boolean;
|
||||
};
|
||||
|
@ -77,7 +77,7 @@ const features = [
|
||||
"Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes."
|
||||
},
|
||||
{
|
||||
_id: 1,
|
||||
id: 1,
|
||||
name: "Infisical Agent",
|
||||
link: "https://infisical.com/docs/infisical-agent/overview",
|
||||
description: "Inject secrets into your apps without modifying any application logic."
|
||||
@ -889,27 +889,27 @@ const OrganizationPage = withPermission(
|
||||
<div className="mt-4 grid w-full grid-cols-3 gap-4">
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature._id}
|
||||
className="flex h-44 w-full flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
key={feature.id}
|
||||
className="relative flex h-full w-full flex-col gap-2 overflow-auto rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
>
|
||||
<div className="mt-0 text-lg text-mineshaft-100">{feature.name}</div>
|
||||
<div className="mb-4 mt-2 text-[15px] font-light text-mineshaft-300">
|
||||
<div className="line-clamp overflwo-auto mb-4 mt-2 h-full text-[15px] font-light text-mineshaft-300">
|
||||
{feature.description}
|
||||
</div>
|
||||
<div className="flex w-full items-center">
|
||||
<div className="text-[15px] font-light text-mineshaft-300">
|
||||
<div className="flex w-full flex-col items-start gap-2 xl:flex-row xl:items-center">
|
||||
<p className="left-0 text-[15px] font-light text-mineshaft-300">
|
||||
Setup time: 20 min
|
||||
</div>
|
||||
</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group ml-auto w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200"
|
||||
className="group ml-0 w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200 xl:ml-auto"
|
||||
href={feature.link}
|
||||
>
|
||||
Learn more{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
|
||||
className="s pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -22,7 +22,9 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
const schema = z
|
||||
.object({
|
||||
description: z.string(),
|
||||
ttl: z.string(),
|
||||
ttl: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "TTL cannot be greater than 315360000"
|
||||
}),
|
||||
numUsesLimit: z.string()
|
||||
})
|
||||
.required();
|
||||
|
@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Modal, ModalContent,Select, SelectItem } from "@app/components/v2";
|
||||
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
useAddIdentityToWorkspace,
|
||||
@ -54,7 +54,7 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle
|
||||
const filteredWorkspaces = useMemo(() => {
|
||||
const wsWorkspaceIds = new Map();
|
||||
|
||||
projectMemberships?.forEach((projectMembership: any) => {
|
||||
projectMemberships?.forEach((projectMembership) => {
|
||||
wsWorkspaceIds.set(projectMembership.project.id, true);
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { IdentityMembership } from "@app/hooks/api/identities/types";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
@ -21,7 +24,7 @@ const formatRoleName = (role: string, customRoleName?: string) => {
|
||||
if (role === ProjectMembershipRole.Admin) return "Admin";
|
||||
if (role === ProjectMembershipRole.Member) return "Developer";
|
||||
if (role === ProjectMembershipRole.Viewer) return "Viewer";
|
||||
if (role === ProjectMembershipRole.NoAccess) return "No access";
|
||||
if (role === ProjectMembershipRole.NoAccess) return "No Access";
|
||||
return role;
|
||||
};
|
||||
|
||||
@ -29,12 +32,34 @@ export const IdentityProjectRow = ({
|
||||
membership: { id, createdAt, identity, project, roles },
|
||||
handlePopUpOpen
|
||||
}: Props) => {
|
||||
const { workspaces } = useWorkspace();
|
||||
const router = useRouter();
|
||||
|
||||
const isAccessible = useMemo(() => {
|
||||
const workspaceIds = new Map();
|
||||
|
||||
workspaces?.forEach((workspace) => {
|
||||
workspaceIds.set(workspace.id, true);
|
||||
});
|
||||
|
||||
return workspaceIds.has(project.id);
|
||||
}, [workspaces, project]);
|
||||
|
||||
return (
|
||||
<Tr
|
||||
className="group h-10 cursor-pointer transition-colors duration-300 hover:bg-mineshaft-700"
|
||||
key={`identity-project-membership-${id}`}
|
||||
onClick={() => router.push(`/project/${project.id}/members`)}
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
router.push(`/project/${project.id}/members`);
|
||||
return;
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: "Unable to access project",
|
||||
type: "error"
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Td>{project.name}</Td>
|
||||
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${
|
||||
@ -42,26 +67,29 @@ export const IdentityProjectRow = ({
|
||||
}`}</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||
<Td>
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Tooltip content="Remove">
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("removeIdentityFromProject", {
|
||||
identityId: identity.id,
|
||||
identityName: identity.name,
|
||||
projectId: project.id,
|
||||
projectName: project.name
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{isAccessible && (
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Tooltip content="Remove">
|
||||
<IconButton
|
||||
colorSchema="danger"
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("removeIdentityFromProject", {
|
||||
identityId: identity.id,
|
||||
identityName: identity.name,
|
||||
projectId: project.id,
|
||||
projectName: project.name
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
@ -3,16 +3,10 @@ import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
import {
|
||||
OrgGroupsTab,
|
||||
OrgIdentityTab,
|
||||
OrgMembersTab,
|
||||
OrgRoleTabSection
|
||||
} from "./components";
|
||||
import { OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components";
|
||||
|
||||
enum TabSections {
|
||||
Member = "members",
|
||||
Groups = "groups",
|
||||
Roles = "roles",
|
||||
Identities = "identities"
|
||||
}
|
||||
@ -25,8 +19,7 @@ export const MembersPage = withPermission(
|
||||
<p className="mr-4 mb-4 text-3xl font-semibold text-white">Organization Access Control</p>
|
||||
<Tabs defaultValue={TabSections.Member}>
|
||||
<TabList>
|
||||
<Tab value={TabSections.Member}>People</Tab>
|
||||
<Tab value={TabSections.Groups}>Groups</Tab>
|
||||
<Tab value={TabSections.Member}>Users</Tab>
|
||||
<Tab value={TabSections.Identities}>
|
||||
<div className="flex items-center">
|
||||
<p>Machine Identities</p>
|
||||
@ -37,9 +30,6 @@ export const MembersPage = withPermission(
|
||||
<TabPanel value={TabSections.Member}>
|
||||
<OrgMembersTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Groups}>
|
||||
<OrgGroupsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Identities}>
|
||||
<OrgIdentityTab />
|
||||
</TabPanel>
|
||||
|
@ -3,16 +3,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { Button, DeleteActionModal, UpgradePlanModal } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { useDeleteGroup } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
@ -21,100 +13,88 @@ import { OrgGroupModal } from "./OrgGroupModal";
|
||||
import { OrgGroupsTable } from "./OrgGroupsTable";
|
||||
|
||||
export const OrgGroupsSection = () => {
|
||||
const { subscription } = useSubscription();
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"group",
|
||||
"groupMembers",
|
||||
"deleteGroup",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const handleAddGroupModal = () => {
|
||||
if (!subscription?.groups) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description: "You can manage users more efficiently with groups if you upgrade your Infisical plan."
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("group");
|
||||
}
|
||||
const { subscription } = useSubscription();
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"group",
|
||||
"groupMembers",
|
||||
"deleteGroup",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const handleAddGroupModal = () => {
|
||||
if (!subscription?.groups) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description:
|
||||
"You can manage users more efficiently with groups if you upgrade your Infisical plan."
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("group");
|
||||
}
|
||||
|
||||
const onDeleteGroupSubmit = async ({
|
||||
name,
|
||||
};
|
||||
|
||||
const onDeleteGroupSubmit = async ({ name, slug }: { name: string; slug: string }) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
slug
|
||||
}: {
|
||||
name: string;
|
||||
slug: string;
|
||||
}) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
slug
|
||||
});
|
||||
createNotification({
|
||||
text: `Successfully deleted the group named ${name}`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to delete the group named ${name}`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
handlePopUpClose("deleteGroup");
|
||||
});
|
||||
createNotification({
|
||||
text: `Successfully deleted the group named ${name}`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to delete the group named ${name}`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Groups</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handleAddGroupModal()}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create Group
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<OrgGroupsTable
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
<OrgGroupModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<OrgGroupMembersModal
|
||||
popUp={popUp}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteGroup.isOpen}
|
||||
title={`Are you sure want to delete the group named ${
|
||||
(popUp?.deleteGroup?.data as { name: string })?.name || ""
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onDeleteGroupSubmit(
|
||||
(popUp?.deleteGroup?.data as { name: string; slug: string })
|
||||
)
|
||||
}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handlePopUpClose("deleteGroup");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">User Groups</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handleAddGroupModal()}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create Group
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<OrgGroupsTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<OrgGroupModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<OrgGroupMembersModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteGroup.isOpen}
|
||||
title={`Are you sure want to delete the group named ${
|
||||
(popUp?.deleteGroup?.data as { name: string })?.name || ""
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; slug: string })
|
||||
}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { faMagnifyingGlass, faPencil, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faEllipsis,faMagnifyingGlass, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
@ -17,218 +21,200 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization} from "@app/context";
|
||||
import {
|
||||
useGetOrganizationGroups,
|
||||
useGetOrgRoles,
|
||||
useUpdateGroup
|
||||
} from "@app/hooks/api";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<
|
||||
["group", "deleteGroup", "groupMembers"]
|
||||
>,
|
||||
data?: {
|
||||
groupId?: string;
|
||||
name?: string;
|
||||
slug?: string;
|
||||
role?: string;
|
||||
customRole?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["group", "deleteGroup", "groupMembers"]>,
|
||||
data?: {
|
||||
groupId?: string;
|
||||
name?: string;
|
||||
slug?: string;
|
||||
role?: string;
|
||||
customRole?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const OrgGroupsTable = ({
|
||||
handlePopUpOpen
|
||||
}: Props) => {
|
||||
const [searchGroupsFilter, setSearchGroupsFilter] = useState("");
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { isLoading, data: groups } = useGetOrganizationGroups(orgId);
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateGroup();
|
||||
|
||||
const { data: roles } = useGetOrgRoles(orgId);
|
||||
|
||||
const handleChangeRole = async ({
|
||||
export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
|
||||
const [searchGroupsFilter, setSearchGroupsFilter] = useState("");
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { isLoading, data: groups } = useGetOrganizationGroups(orgId);
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateGroup();
|
||||
|
||||
const { data: roles } = useGetOrgRoles(orgId);
|
||||
|
||||
const handleChangeRole = async ({ currentSlug, role }: { currentSlug: string; role: string }) => {
|
||||
try {
|
||||
await updateMutateAsync({
|
||||
currentSlug,
|
||||
role
|
||||
}: {
|
||||
currentSlug: string;
|
||||
role: string;
|
||||
}) => {
|
||||
try {
|
||||
await updateMutateAsync({
|
||||
currentSlug,
|
||||
role
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated group role",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to update group role",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated group role",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to update group role",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={searchGroupsFilter}
|
||||
onChange={(e) => setSearchGroupsFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search groups..."
|
||||
/>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Slug</Th>
|
||||
<Th>Role</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="org-groups" />}
|
||||
{!isLoading && groups?.map(({ id, name, slug, role, customRole }) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`org-group-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>{slug}</Td>
|
||||
<Td>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Select
|
||||
value={role === "custom" ? (customRole?.slug as string) : role}
|
||||
isDisabled={!isAllowed}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={(selectedRole) =>
|
||||
handleChangeRole({
|
||||
currentSlug: slug,
|
||||
role: selectedRole
|
||||
})
|
||||
}
|
||||
>
|
||||
{(roles || []).map(({ slug: roleSlug, name: roleName }) => (
|
||||
<SelectItem value={roleSlug} key={`role-option-${roleSlug}`}>
|
||||
{roleName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
</OrgPermissionCan>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Tooltip content="Manage group members">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("groupMembers", {
|
||||
slug
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Tooltip content="Edit group">
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
handlePopUpOpen("group", {
|
||||
groupId: id,
|
||||
name,
|
||||
slug,
|
||||
role,
|
||||
customRole
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Tooltip content="Delete group">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteGroup", {
|
||||
slug,
|
||||
name
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{groups?.length === 0 && (
|
||||
<EmptyState title="No groups found" icon={faUsers} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={searchGroupsFilter}
|
||||
onChange={(e) => setSearchGroupsFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search groups..."
|
||||
/>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Slug</Th>
|
||||
<Th>Role</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="org-groups" />}
|
||||
{!isLoading &&
|
||||
groups?.map(({ id, name, slug, role, customRole }) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`org-group-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>{slug}</Td>
|
||||
<Td>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Select
|
||||
value={role === "custom" ? (customRole?.slug as string) : role}
|
||||
isDisabled={!isAllowed}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={(selectedRole) =>
|
||||
handleChangeRole({
|
||||
currentSlug: slug,
|
||||
role: selectedRole
|
||||
})
|
||||
}
|
||||
>
|
||||
{(roles || []).map(({ slug: roleSlug, name: roleName }) => (
|
||||
<SelectItem value={roleSlug} key={`role-option-${roleSlug}`}>
|
||||
{roleName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
</OrgPermissionCan>
|
||||
</Td>
|
||||
<Td>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("groupMembers", {
|
||||
slug
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
Manage Users
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("group", {
|
||||
groupId: id,
|
||||
name,
|
||||
slug,
|
||||
role,
|
||||
customRole
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
Edit Group
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
isAllowed
|
||||
? "hover:!bg-red-500 hover:!text-white"
|
||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("deleteGroup", {
|
||||
slug,
|
||||
name
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
Delete Group
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{groups?.length === 0 && <EmptyState title="No groups found" icon={faUsers} />}
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -22,8 +22,22 @@ const schema = yup
|
||||
stsEndpoint: yup.string(),
|
||||
allowedPrincipalArns: yup.string(),
|
||||
allowedAccountIds: yup.string(),
|
||||
accessTokenTTL: yup.string().required("Access Token TTL is required"),
|
||||
accessTokenMaxTTL: yup.string().required("Access Max Token TTL is required"),
|
||||
accessTokenTTL: yup
|
||||
.string()
|
||||
.required("Access Token TTL is required")
|
||||
.test(
|
||||
"is-value-valid",
|
||||
"Access Token TTL cannot be greater than 315360000",
|
||||
(value) => Number(value) <= 315360000
|
||||
),
|
||||
accessTokenMaxTTL: yup
|
||||
.string()
|
||||
.required("Access Max Token TTL is required")
|
||||
.test(
|
||||
"is-value-valid",
|
||||
"Access Token Max TTL cannot be greater than 315360000",
|
||||
(value) => Number(value) <= 315360000
|
||||
),
|
||||
accessTokenNumUsesLimit: yup.string().required("Access Token Max Number of Uses is required"),
|
||||
accessTokenTrustedIps: yup
|
||||
.array(
|
||||
|
@ -22,8 +22,12 @@ const schema = z
|
||||
tenantId: z.string(),
|
||||
resource: z.string(),
|
||||
allowedServicePrincipalIds: z.string(),
|
||||
accessTokenTTL: z.string(),
|
||||
accessTokenMaxTTL: z.string(),
|
||||
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
accessTokenTrustedIps: z
|
||||
.array(
|
||||
|
@ -23,8 +23,12 @@ const schema = z
|
||||
allowedServiceAccounts: z.string(),
|
||||
allowedProjects: z.string(),
|
||||
allowedZones: z.string(),
|
||||
accessTokenTTL: z.string(),
|
||||
accessTokenMaxTTL: z.string(),
|
||||
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
accessTokenTrustedIps: z
|
||||
.array(
|
||||
|
@ -25,8 +25,12 @@ const schema = z
|
||||
allowedNamespaces: z.string(),
|
||||
allowedAudience: z.string(),
|
||||
caCert: z.string(),
|
||||
accessTokenTTL: z.string(),
|
||||
accessTokenMaxTTL: z.string(),
|
||||
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
accessTokenTrustedIps: z
|
||||
.array(
|
||||
|
@ -22,8 +22,12 @@ const schema = z.object({
|
||||
})
|
||||
)
|
||||
.min(1),
|
||||
accessTokenTTL: z.string(),
|
||||
accessTokenMaxTTL: z.string(),
|
||||
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
oidcDiscoveryUrl: z.string().url().min(1),
|
||||
caCert: z.string().trim().default(""),
|
||||
|
@ -108,7 +108,7 @@ export const IdentitySection = withPermission(
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<IdentityTable />
|
||||
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
{/* <IdentityAuthMethodModal
|
||||
popUp={popUp}
|
||||
|
@ -1,13 +1,16 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faEllipsis, faServer } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Select,
|
||||
SelectItem,
|
||||
Table,
|
||||
@ -21,8 +24,19 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useGetIdentityMembershipOrgs, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
export const IdentityTable = () => {
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteIdentity"]>,
|
||||
data?: {
|
||||
identityId: string;
|
||||
name: string;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
const router = useRouter();
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
@ -76,9 +90,7 @@ export const IdentityTable = () => {
|
||||
key={`identity-${id}`}
|
||||
onClick={() => router.push(`/org/${orgId}/identities/${id}`)}
|
||||
>
|
||||
<Td>
|
||||
<Link href={`/org/${orgId}/identities/${id}`}>{name}</Link>
|
||||
</Td>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
@ -109,16 +121,58 @@ export const IdentityTable = () => {
|
||||
</OrgPermissionCan>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end space-x-4">
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={() => router.push(`/org/${orgId}/identities/${id}`)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsis} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`/org/${orgId}/identities/${id}`);
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
Edit Identity
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
isAllowed
|
||||
? "hover:!bg-red-500 hover:!text-white"
|
||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("deleteIdentity", {
|
||||
identityId: id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
Delete Identity
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
@ -17,8 +17,12 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
accessTokenTTL: z.string(),
|
||||
accessTokenMaxTTL: z.string(),
|
||||
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||
}),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
accessTokenTrustedIps: z
|
||||
.array(
|
||||
|
@ -36,7 +36,13 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = yup.object({
|
||||
description: yup.string(),
|
||||
ttl: yup.string(),
|
||||
ttl: yup
|
||||
.string()
|
||||
.test(
|
||||
"is-value-valid",
|
||||
"TTL cannot be greater than 315360000",
|
||||
(value) => Number(value) <= 315360000
|
||||
),
|
||||
numUsesLimit: yup.string()
|
||||
});
|
||||
|
||||
|
@ -19,8 +19,22 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = yup
|
||||
.object({
|
||||
accessTokenTTL: yup.string().required("Access Token TTL is required"),
|
||||
accessTokenMaxTTL: yup.string().required("Access Max Token TTL is required"),
|
||||
accessTokenTTL: yup
|
||||
.string()
|
||||
.required("Access Token TTL is required")
|
||||
.test(
|
||||
"is-value-valid",
|
||||
"Access Token TTL cannot be greater than 315360000",
|
||||
(value) => Number(value) <= 315360000
|
||||
),
|
||||
accessTokenMaxTTL: yup
|
||||
.string()
|
||||
.required("Access Max Token TTL is required")
|
||||
.test(
|
||||
"is-value-valid",
|
||||
"Access Max Token TTL cannot be greater than 315360000",
|
||||
(value) => Number(value) <= 315360000
|
||||
),
|
||||
accessTokenNumUsesLimit: yup.string().required("Access Token Max Number of Uses is required"),
|
||||
clientSecretTrustedIps: yup
|
||||
.array(
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { OrgGroupsSection } from "../OrgGroupsTab/components";
|
||||
import { OrgMembersSection } from "./components";
|
||||
|
||||
export const OrgMembersTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-org-members"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
key="panel-org-members"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<OrgMembersSection />
|
||||
<OrgMembersSection />
|
||||
<OrgGroupsSection />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -90,7 +90,7 @@ export const OrgMembersSection = () => {
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Members</p>
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Users</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
|
@ -1,17 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
|
||||
import {
|
||||
GroupsTab,
|
||||
IdentityTab,
|
||||
MemberListTab,
|
||||
ProjectRoleListTab,
|
||||
ServiceTokenTab
|
||||
} from "./components";
|
||||
import { IdentityTab, MembersTab,ProjectRoleListTab, ServiceTokenTab } from "./components";
|
||||
|
||||
enum TabSections {
|
||||
Member = "members",
|
||||
@ -23,17 +15,13 @@ enum TabSections {
|
||||
|
||||
export const MembersPage = withProjectPermission(
|
||||
() => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||
<p className="mr-4 mb-4 text-3xl font-semibold text-white">Project Access Control</p>
|
||||
<Tabs defaultValue={TabSections.Member}>
|
||||
<TabList>
|
||||
<Tab value={TabSections.Member}>People</Tab>
|
||||
{currentWorkspace?.version && currentWorkspace.version > 1 && (
|
||||
<Tab value={TabSections.Groups}>Groups</Tab>
|
||||
)}
|
||||
<Tab value={TabSections.Member}>Users</Tab>
|
||||
<Tab value={TabSections.Identities}>
|
||||
<div className="flex items-center">
|
||||
<p>Machine Identities</p>
|
||||
@ -43,21 +31,8 @@ export const MembersPage = withProjectPermission(
|
||||
<Tab value={TabSections.Roles}>Project Roles</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Member}>
|
||||
<MemberListTab />
|
||||
<MembersTab />
|
||||
</TabPanel>
|
||||
{currentWorkspace?.version && currentWorkspace.version > 1 && (
|
||||
<TabPanel value={TabSections.Groups}>
|
||||
<motion.div
|
||||
key="panel-groups"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<GroupsTab />
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
)}
|
||||
<TabPanel value={TabSections.Identities}>
|
||||
<IdentityTab />
|
||||
</TabPanel>
|
||||
|
@ -3,12 +3,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal, UpgradePlanModal } from "@app/components/v2";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useSubscription,useWorkspace } from "@app/context";
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useSubscription,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteGroupFromWorkspace } from "@app/hooks/api";
|
||||
|
||||
@ -16,90 +17,89 @@ import { GroupModal } from "./GroupModal";
|
||||
import { GroupTable } from "./GroupsTable";
|
||||
|
||||
export const GroupsSection = () => {
|
||||
const { subscription } = useSubscription();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { subscription } = useSubscription();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteGroupFromWorkspace();
|
||||
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"group",
|
||||
"deleteGroup",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteGroupFromWorkspace();
|
||||
|
||||
const handleAddGroupModal = () => {
|
||||
if (!subscription?.groups) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description: "You can manage users more efficiently with groups if you upgrade your Infisical plan."
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("group");
|
||||
}
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"group",
|
||||
"deleteGroup",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const handleAddGroupModal = () => {
|
||||
if (!subscription?.groups) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description:
|
||||
"You can manage users more efficiently with groups if you upgrade your Infisical plan."
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("group");
|
||||
}
|
||||
};
|
||||
|
||||
const onRemoveGroupSubmit = async (groupSlug: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
groupSlug,
|
||||
projectSlug: currentWorkspace?.slug || ""
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed identity from project",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteGroup");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to remove group from project";
|
||||
|
||||
createNotification({
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
const onRemoveGroupSubmit = async (groupSlug: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
groupSlug,
|
||||
projectSlug: currentWorkspace?.slug || ""
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed identity from project",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteGroup");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to remove group from project";
|
||||
|
||||
createNotification({
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">User Groups</p>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handleAddGroupModal()}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Group
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<GroupModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<GroupTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteGroup.isOpen}
|
||||
title={`Are you sure want to remove the group ${
|
||||
(popUp?.deleteGroup?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onRemoveGroupSubmit((popUp?.deleteGroup?.data as { slug: string })?.slug)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Groups</p>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Groups}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handleAddGroupModal()}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Group
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<GroupModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<GroupTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteGroup.isOpen}
|
||||
title={`Are you sure want to remove the group ${
|
||||
(popUp?.deleteGroup?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onRemoveGroupSubmit(
|
||||
(popUp?.deleteGroup?.data as { slug: string })?.slug
|
||||
)
|
||||
}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faServer, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
@ -36,68 +37,71 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
|
||||
const { data, isLoading } = useListWorkspaceGroups(currentWorkspace?.slug || "");
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Role</Th>
|
||||
<Th>Added on</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="project-groups" />}
|
||||
{!isLoading &&
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map(({ group: { id, name, slug }, roles, createdAt }) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`st-v3-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Groups}
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Role</Th>
|
||||
<Th>Added on</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="project-groups" />}
|
||||
{!isLoading &&
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map(({ group: { id, name, slug }, roles, createdAt }) => {
|
||||
return (
|
||||
<Tr className="group h-10" key={`st-v3-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<GroupRoles roles={roles} disableEdit={!isAllowed} groupSlug={slug} />
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Tooltip content="Remove">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteGroup", {
|
||||
slug,
|
||||
name
|
||||
});
|
||||
}}
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<GroupRoles roles={roles} disableEdit={!isAllowed} groupSlug={slug} />
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Groups}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteGroup", {
|
||||
slug,
|
||||
name
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isLoading && data?.length === 0 && (
|
||||
<EmptyState title="No groups have been added to this project" icon={faServer} />
|
||||
)}
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isLoading && data?.length === 0 && (
|
||||
<EmptyState title="No groups have been added to this project" icon={faServer} />
|
||||
)}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,505 +0,0 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
faClock,
|
||||
faEdit,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faUsers,
|
||||
faXmark
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
FormControl,
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
Tag,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useOrganization,
|
||||
useUser,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useAddUserToWsE2EE,
|
||||
useAddUserToWsNonE2EE,
|
||||
useDeleteUserFromWorkspace,
|
||||
useGetOrgUsers,
|
||||
useGetUserWsKey,
|
||||
useGetWorkspaceUsers
|
||||
} from "@app/hooks/api";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { MemberRoleForm } from "./MemberRoleForm";
|
||||
|
||||
const addMemberFormSchema = z.object({
|
||||
orgMembershipId: z.string().trim()
|
||||
});
|
||||
|
||||
type TAddMemberForm = z.infer<typeof addMemberFormSchema>;
|
||||
|
||||
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
|
||||
const formatRoleName = (role: string, customRoleName?: string) => {
|
||||
if (role === ProjectMembershipRole.Custom) return customRoleName;
|
||||
if (role === ProjectMembershipRole.Member) return "Developer";
|
||||
if (role === ProjectMembershipRole.NoAccess) return "No access";
|
||||
return role;
|
||||
};
|
||||
|
||||
export const MemberListTab = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { user } = useUser();
|
||||
|
||||
const userId = user?.id || "";
|
||||
const orgId = currentOrg?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
|
||||
const { data: wsKey } = useGetUserWsKey(workspaceId);
|
||||
const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId);
|
||||
const { data: orgUsers } = useGetOrgUsers(orgId);
|
||||
|
||||
const [searchMemberFilter, setSearchMemberFilter] = useState("");
|
||||
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"addMember",
|
||||
"removeMember",
|
||||
"upgradePlan",
|
||||
"updateRole"
|
||||
] as const);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TAddMemberForm>({ resolver: zodResolver(addMemberFormSchema) });
|
||||
|
||||
const { mutateAsync: addUserToWorkspace } = useAddUserToWsE2EE();
|
||||
const { mutateAsync: addUserToWorkspaceNonE2EE } = useAddUserToWsNonE2EE();
|
||||
const { mutateAsync: removeUserFromWorkspace } = useDeleteUserFromWorkspace();
|
||||
|
||||
const onAddMember = async ({ orgMembershipId }: TAddMemberForm) => {
|
||||
if (!currentWorkspace) return;
|
||||
if (!currentOrg?.id) return;
|
||||
// TODO(akhilmhdh): Move to memory storage
|
||||
const userPrivateKey = localStorage.getItem("PRIVATE_KEY");
|
||||
if (!userPrivateKey || !wsKey) {
|
||||
createNotification({
|
||||
text: "Failed to find private key. Try re-login"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const orgUser = (orgUsers || []).find(({ id }) => id === orgMembershipId);
|
||||
if (!orgUser) return;
|
||||
|
||||
try {
|
||||
// TODO: update
|
||||
if (currentWorkspace.version === ProjectVersion.V1) {
|
||||
await addUserToWorkspace({
|
||||
workspaceId,
|
||||
userPrivateKey,
|
||||
decryptKey: wsKey,
|
||||
members: [{ orgMembershipId, userPublicKey: orgUser.user.publicKey }]
|
||||
});
|
||||
} else if (currentWorkspace.version === ProjectVersion.V2) {
|
||||
await addUserToWorkspaceNonE2EE({
|
||||
projectId: workspaceId,
|
||||
usernames: [orgUser.user.username]
|
||||
});
|
||||
} else {
|
||||
createNotification({
|
||||
text: "Failed to add user to project, unknown project type",
|
||||
type: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
createNotification({
|
||||
text: "Successfully added user to the project",
|
||||
type: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
text: "Failed to add user to project",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
handlePopUpClose("addMember");
|
||||
reset();
|
||||
};
|
||||
|
||||
const handleRemoveUser = async () => {
|
||||
const username = (popUp?.removeMember?.data as { username: string })?.username;
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
try {
|
||||
await removeUserFromWorkspace({ workspaceId, usernames: [username] });
|
||||
createNotification({
|
||||
text: "Successfully removed user from project",
|
||||
type: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
text: "Failed to remove user from the project",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
handlePopUpClose("removeMember");
|
||||
};
|
||||
|
||||
const filterdUsers = useMemo(
|
||||
() =>
|
||||
members?.filter(
|
||||
({ user: u, inviteEmail }) =>
|
||||
u?.firstName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
u?.lastName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
u?.username?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
u?.email?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
inviteEmail?.includes(searchMemberFilter.toLowerCase())
|
||||
),
|
||||
[members, searchMemberFilter]
|
||||
);
|
||||
|
||||
const filteredOrgUsers = useMemo(() => {
|
||||
const wsUserUsernames = new Map();
|
||||
members?.forEach((member) => {
|
||||
wsUserUsernames.set(member.user.username, true);
|
||||
});
|
||||
return (orgUsers || []).filter(
|
||||
({ status, user: u }) => status === "accepted" && !wsUserUsernames.has(u.username)
|
||||
);
|
||||
}, [orgUsers, members]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="user-role-1"
|
||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Members</p>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("addMember")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<Input
|
||||
value={searchMemberFilter}
|
||||
onChange={(e) => setSearchMemberFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search members..."
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Username</Th>
|
||||
<Th>Role</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isMembersLoading && <TableSkeleton columns={4} innerKey="project-members" />}
|
||||
{!isMembersLoading &&
|
||||
filterdUsers?.map((projectMember, index) => {
|
||||
const { user: u, inviteEmail, id: membershipId, roles } = projectMember;
|
||||
const name = u ? `${u.firstName} ${u.lastName}` : "-";
|
||||
const email = u?.email || inviteEmail;
|
||||
|
||||
return (
|
||||
<Tr key={`membership-${membershipId}`} className="w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{email}</Td>
|
||||
<Td>
|
||||
<div className="flex items-center space-x-2">
|
||||
{roles
|
||||
.slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
id,
|
||||
isTemporary,
|
||||
temporaryAccessEndTime
|
||||
}) => {
|
||||
const isExpired =
|
||||
new Date() > new Date(temporaryAccessEndTime || ("" as string));
|
||||
return (
|
||||
<Tag key={id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="capitalize">
|
||||
{formatRoleName(role, customRoleName)}
|
||||
</div>
|
||||
{isTemporary && (
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
isExpired ? "Timed role expired" : "Timed role access"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className={twMerge(isExpired && "text-red-600")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{roles.length > MAX_ROLES_TO_BE_SHOWN_IN_TABLE && (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Tag>+{roles.length - MAX_ROLES_TO_BE_SHOWN_IN_TABLE}</Tag>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="border border-gray-700 bg-mineshaft-800 p-4">
|
||||
{roles
|
||||
.slice(MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
id,
|
||||
isTemporary,
|
||||
temporaryAccessEndTime
|
||||
}) => {
|
||||
const isExpired =
|
||||
new Date() >
|
||||
new Date(temporaryAccessEndTime || ("" as string));
|
||||
return (
|
||||
<Tag key={id} className="capitalize">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{formatRoleName(role, customRoleName)}</div>
|
||||
{isTemporary && (
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
isExpired
|
||||
? "Access expired"
|
||||
: "Temporary access"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className={twMerge(
|
||||
new Date() >
|
||||
new Date(
|
||||
temporaryAccessEndTime as string
|
||||
) && "text-red-600"
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)}
|
||||
{userId !== u?.id && (
|
||||
<Tooltip content="Edit permission">
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="plain"
|
||||
ariaLabel="update-role"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("updateRole", { ...projectMember, index })
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{userId !== u?.id && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Member}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={userId === u?.id || !isAllowed}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("removeMember", { username: u.username })
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isMembersLoading && filterdUsers?.length === 0 && (
|
||||
<EmptyState title="No project members found" icon={faUsers} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={popUp?.addMember?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addMember", isOpen)}
|
||||
>
|
||||
<ModalContent
|
||||
title={t("section.members.add-dialog.add-member-to-project") as string}
|
||||
subTitle={t("section.members.add-dialog.user-will-email")}
|
||||
>
|
||||
{filteredOrgUsers.length ? (
|
||||
<form onSubmit={handleSubmit(onAddMember)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.username}
|
||||
name="orgMembershipId"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Username" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Select
|
||||
position="popper"
|
||||
className="w-full"
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.username}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{filteredOrgUsers.map(({ id: orgUserId, user: u }) => (
|
||||
<SelectItem value={orgUserId} key={`org-membership-join-${orgUserId}`}>
|
||||
{u?.username}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpClose("addMember")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>All the users in your organization are already invited.</div>
|
||||
<Link href={`/org/${currentWorkspace?.orgId}/members`}>
|
||||
<Button variant="outline_bg">Add users to organization</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal
|
||||
isOpen={popUp.updateRole.isOpen}
|
||||
onOpenChange={(state) => handlePopUpToggle("updateRole", state)}
|
||||
>
|
||||
<ModalContent
|
||||
className="max-w-4xl"
|
||||
title={`Manage Access for ${(popUp.updateRole.data as TWorkspaceUser)?.user?.email}`}
|
||||
subTitle={`
|
||||
Configure role-based access control by assigning Infisical users a mix of roles and specific privileges. A user will gain access to all actions within the roles assigned to them, not just the actions those roles share in common. You must choose at least one permanent role.
|
||||
`}
|
||||
>
|
||||
<MemberRoleForm
|
||||
onOpenUpgradeModal={(description) => handlePopUpOpen("upgradePlan", { description })}
|
||||
projectMember={
|
||||
filterdUsers?.[
|
||||
(popUp.updateRole?.data as TWorkspaceUser & { index: number })?.index
|
||||
] as TWorkspaceUser
|
||||
}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.removeMember.isOpen}
|
||||
deleteKey="remove"
|
||||
title="Do you want to remove this user from the project?"
|
||||
onChange={(isOpen) => handlePopUpToggle("removeMember", isOpen)}
|
||||
onDeleteApproved={handleRemoveUser}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { MemberListTab } from "./MemberListTab";
|
@ -0,0 +1,22 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { useWorkspace } from "@app/context";
|
||||
|
||||
import { GroupsSection } from "../GroupsTab/components";
|
||||
import { MembersSection } from "./components";
|
||||
|
||||
export const MembersTab = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-project-members"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<MembersSection />
|
||||
{currentWorkspace?.version && currentWorkspace.version > 1 && <GroupsSection />}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
@ -0,0 +1,177 @@
|
||||
import { useMemo } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button,FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useAddUserToWsE2EE,
|
||||
useAddUserToWsNonE2EE,
|
||||
useGetOrgUsers,
|
||||
useGetUserWsKey,
|
||||
useGetWorkspaceUsers} from "@app/hooks/api";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const addMemberFormSchema = z.object({
|
||||
orgMembershipId: z.string().trim()
|
||||
});
|
||||
|
||||
type TAddMemberForm = z.infer<typeof addMemberFormSchema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["addMember"]>;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addMember"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const orgId = currentOrg?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
|
||||
const { data: wsKey } = useGetUserWsKey(workspaceId);
|
||||
const { data: members } = useGetWorkspaceUsers(workspaceId);
|
||||
const { data: orgUsers } = useGetOrgUsers(orgId);
|
||||
|
||||
const { mutateAsync: addUserToWorkspace } = useAddUserToWsE2EE();
|
||||
const { mutateAsync: addUserToWorkspaceNonE2EE } = useAddUserToWsNonE2EE();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TAddMemberForm>({ resolver: zodResolver(addMemberFormSchema) });
|
||||
|
||||
const onAddMember = async ({ orgMembershipId }: TAddMemberForm) => {
|
||||
if (!currentWorkspace) return;
|
||||
if (!currentOrg?.id) return;
|
||||
// TODO(akhilmhdh): Move to memory storage
|
||||
const userPrivateKey = localStorage.getItem("PRIVATE_KEY");
|
||||
if (!userPrivateKey || !wsKey) {
|
||||
createNotification({
|
||||
text: "Failed to find private key. Try re-login"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const orgUser = (orgUsers || []).find(({ id }) => id === orgMembershipId);
|
||||
if (!orgUser) return;
|
||||
|
||||
try {
|
||||
// TODO: update
|
||||
if (currentWorkspace.version === ProjectVersion.V1) {
|
||||
await addUserToWorkspace({
|
||||
workspaceId,
|
||||
userPrivateKey,
|
||||
decryptKey: wsKey,
|
||||
members: [{ orgMembershipId, userPublicKey: orgUser.user.publicKey }]
|
||||
});
|
||||
} else if (currentWorkspace.version === ProjectVersion.V2) {
|
||||
await addUserToWorkspaceNonE2EE({
|
||||
projectId: workspaceId,
|
||||
usernames: [orgUser.user.username]
|
||||
});
|
||||
} else {
|
||||
createNotification({
|
||||
text: "Failed to add user to project, unknown project type",
|
||||
type: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
createNotification({
|
||||
text: "Successfully added user to the project",
|
||||
type: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
text: "Failed to add user to project",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
handlePopUpToggle("addMember", false);
|
||||
reset();
|
||||
};
|
||||
|
||||
const filteredOrgUsers = useMemo(() => {
|
||||
const wsUserUsernames = new Map();
|
||||
members?.forEach((member) => {
|
||||
wsUserUsernames.set(member.user.username, true);
|
||||
});
|
||||
return (orgUsers || []).filter(
|
||||
({ status, user: u }) => status === "accepted" && !wsUserUsernames.has(u.username)
|
||||
);
|
||||
}, [orgUsers, members]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addMember?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addMember", isOpen)}
|
||||
>
|
||||
<ModalContent
|
||||
title={t("section.members.add-dialog.add-member-to-project") as string}
|
||||
subTitle={t("section.members.add-dialog.user-will-email")}
|
||||
>
|
||||
{filteredOrgUsers.length ? (
|
||||
<form onSubmit={handleSubmit(onAddMember)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.username}
|
||||
name="orgMembershipId"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Username" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Select
|
||||
position="popper"
|
||||
className="w-full"
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.username}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{filteredOrgUsers.map(({ id: orgUserId, user: u }) => (
|
||||
<SelectItem value={orgUserId} key={`org-membership-join-${orgUserId}`}>
|
||||
{u?.username}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("addMember", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>All the users in your organization are already invited.</div>
|
||||
<Link href={`/org/${currentWorkspace?.orgId}/members`}>
|
||||
<Button variant="outline_bg">Add users to organization</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -0,0 +1,86 @@
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal, UpgradePlanModal } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub , useOrganization, useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteUserFromWorkspace } from "@app/hooks/api";
|
||||
|
||||
import { AddMemberModal } from "./AddMemberModal";
|
||||
import { MembersTable } from "./MembersTable";
|
||||
|
||||
export const MembersSection = () => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { mutateAsync: removeUserFromWorkspace } = useDeleteUserFromWorkspace();
|
||||
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"addMember",
|
||||
"removeMember",
|
||||
"upgradePlan",
|
||||
"updateRole"
|
||||
] as const);
|
||||
|
||||
const handleRemoveUser = async () => {
|
||||
const username = (popUp?.removeMember?.data as { username: string })?.username;
|
||||
if (!currentOrg?.id) return;
|
||||
if (!currentWorkspace?.id) return;
|
||||
|
||||
try {
|
||||
await removeUserFromWorkspace({ workspaceId: currentWorkspace.id, usernames: [username] });
|
||||
createNotification({
|
||||
text: "Successfully removed user from project",
|
||||
type: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
text: "Failed to remove user from the project",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
handlePopUpClose("removeMember");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Users</p>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("addMember")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<MembersTable
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<AddMemberModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.removeMember.isOpen}
|
||||
deleteKey="remove"
|
||||
title="Do you want to remove this user from the project?"
|
||||
onChange={(isOpen) => handlePopUpToggle("removeMember", isOpen)}
|
||||
onDeleteApproved={handleRemoveUser}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,269 @@
|
||||
import { useMemo,useState } from "react";
|
||||
import {
|
||||
faClock,
|
||||
faEdit,
|
||||
faMagnifyingGlass,
|
||||
faTrash,
|
||||
faUsers} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
EmptyState,
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
Tag,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useUser,
|
||||
useWorkspace} from "@app/context";
|
||||
import { useGetWorkspaceUsers } from "@app/hooks/api";
|
||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { MemberRoleForm } from "./MemberRoleForm";
|
||||
|
||||
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
|
||||
const formatRoleName = (role: string, customRoleName?: string) => {
|
||||
if (role === ProjectMembershipRole.Custom) return customRoleName;
|
||||
if (role === ProjectMembershipRole.Member) return "Developer";
|
||||
if (role === ProjectMembershipRole.NoAccess) return "No access";
|
||||
return role;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["updateRole"]>;
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["removeMember", "updateRole", "upgradePlan"]>,
|
||||
data?: {}
|
||||
) => void;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["updateRole"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => {
|
||||
const [searchMemberFilter, setSearchMemberFilter] = useState("");
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { user } = useUser();
|
||||
|
||||
const userId = user?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
|
||||
const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId);
|
||||
|
||||
const filterdUsers = useMemo(
|
||||
() =>
|
||||
members?.filter(
|
||||
({ user: u, inviteEmail }) =>
|
||||
u?.firstName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
u?.lastName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
u?.username?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
u?.email?.toLowerCase().includes(searchMemberFilter.toLowerCase()) ||
|
||||
inviteEmail?.includes(searchMemberFilter.toLowerCase())
|
||||
),
|
||||
[members, searchMemberFilter]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={searchMemberFilter}
|
||||
onChange={(e) => setSearchMemberFilter(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search members..."
|
||||
/>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Username</Th>
|
||||
<Th>Role</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isMembersLoading && <TableSkeleton columns={4} innerKey="project-members" />}
|
||||
{!isMembersLoading &&
|
||||
filterdUsers?.map((projectMember, index) => {
|
||||
const { user: u, inviteEmail, id: membershipId, roles } = projectMember;
|
||||
const name = u ? `${u.firstName} ${u.lastName}` : "-";
|
||||
const email = u?.email || inviteEmail;
|
||||
|
||||
return (
|
||||
<Tr key={`membership-${membershipId}`} className="group w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{email}</Td>
|
||||
<Td>
|
||||
<div className="flex items-center space-x-2">
|
||||
{roles
|
||||
.slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({ role, customRoleName, id, isTemporary, temporaryAccessEndTime }) => {
|
||||
const isExpired =
|
||||
new Date() > new Date(temporaryAccessEndTime || ("" as string));
|
||||
return (
|
||||
<Tag key={id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="capitalize">
|
||||
{formatRoleName(role, customRoleName)}
|
||||
</div>
|
||||
{isTemporary && (
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
isExpired ? "Timed role expired" : "Timed role access"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className={twMerge(isExpired && "text-red-600")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{roles.length > MAX_ROLES_TO_BE_SHOWN_IN_TABLE && (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Tag>+{roles.length - MAX_ROLES_TO_BE_SHOWN_IN_TABLE}</Tag>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="border border-gray-700 bg-mineshaft-800 p-4">
|
||||
{roles
|
||||
.slice(MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
id,
|
||||
isTemporary,
|
||||
temporaryAccessEndTime
|
||||
}) => {
|
||||
const isExpired =
|
||||
new Date() >
|
||||
new Date(temporaryAccessEndTime || ("" as string));
|
||||
return (
|
||||
<Tag key={id} className="capitalize">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{formatRoleName(role, customRoleName)}</div>
|
||||
{isTemporary && (
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
isExpired ? "Access expired" : "Temporary access"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className={twMerge(
|
||||
new Date() >
|
||||
new Date(temporaryAccessEndTime as string) &&
|
||||
"text-red-600"
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)}
|
||||
{userId !== u?.id && (
|
||||
<Tooltip content="Edit permission">
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="plain"
|
||||
ariaLabel="update-role"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("updateRole", { ...projectMember, index })
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{userId !== u?.id && (
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Member}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={userId === u?.id || !isAllowed}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("removeMember", { username: u.username })
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isMembersLoading && filterdUsers?.length === 0 && (
|
||||
<EmptyState title="No project members found" icon={faUsers} />
|
||||
)}
|
||||
</TableContainer>
|
||||
<Modal
|
||||
isOpen={popUp.updateRole.isOpen}
|
||||
onOpenChange={(state) => handlePopUpToggle("updateRole", state)}
|
||||
>
|
||||
<ModalContent
|
||||
className="max-w-4xl"
|
||||
title={`Manage Access for ${(popUp.updateRole.data as TWorkspaceUser)?.user?.email}`}
|
||||
subTitle={`
|
||||
Configure role-based access control by assigning Infisical users a mix of roles and specific privileges. A user will gain access to all actions within the roles assigned to them, not just the actions those roles share in common. You must choose at least one permanent role.
|
||||
`}
|
||||
>
|
||||
<MemberRoleForm
|
||||
onOpenUpgradeModal={(description) => handlePopUpOpen("upgradePlan", { description })}
|
||||
projectMember={
|
||||
filterdUsers?.[
|
||||
(popUp.updateRole?.data as TWorkspaceUser & { index: number })?.index
|
||||
] as TWorkspaceUser
|
||||
}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export { MembersSection } from "./MembersSection";
|
@ -0,0 +1 @@
|
||||
export { MembersTab } from "./MembersTab";
|
@ -1,5 +1,5 @@
|
||||
export { GroupsTab } from "./GroupsTab";
|
||||
export { IdentityTab } from "./IdentityTab";
|
||||
export { MemberListTab } from "./MemberListTab";
|
||||
export { MembersTab } from "./MembersTab";
|
||||
export { ProjectRoleListTab } from "./ProjectRoleListTab";
|
||||
export { ServiceTokenTab } from "./ServiceTokenTab";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { TAccessApprovalPolicy } from "@app/hooks/api/types";
|
||||
import { SpecificPrivilegeSecretForm } from "@app/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection";
|
||||
import { SpecificPrivilegeSecretForm } from "@app/views/Project/MembersPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection";
|
||||
|
||||
export const RequestAccessModal = ({
|
||||
isOpen,
|
||||
|
@ -77,7 +77,7 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
|
||||
</div>
|
||||
<div className="w-full flex justify-center">
|
||||
<h1 className={`${id ? "max-w-sm mb-4": "max-w-md mt-4 mb-6"} bg-gradient-to-b from-white to-bunker-200 bg-clip-text px-4 text-center text-3xl font-medium text-transparent`}>
|
||||
{id ? "Someone shared a secret on Infisical with you" : "Share a secret with Infisical"}
|
||||
{id ? "Someone shared a secret via Infisical with you" : "Share a secret via Infisical"}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="m-auto mt-4 flex w-full max-w-2xl justify-center px-6">
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { faCheck, faCopy, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheck, faCopy, faEye, faEyeSlash, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { EmptyState, IconButton, Td, Tr } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
type Props = {
|
||||
isLoading: boolean;
|
||||
@ -10,39 +11,68 @@ type Props = {
|
||||
copyUrlToClipboard: () => void;
|
||||
};
|
||||
|
||||
const replaceContentWithDot = (str: string) => {
|
||||
let finalStr = "";
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
const char = str.at(i);
|
||||
finalStr += char === "\n" ? "\n" : "*";
|
||||
}
|
||||
return finalStr;
|
||||
};
|
||||
|
||||
export const SecretTable = ({
|
||||
isLoading,
|
||||
decryptedSecret,
|
||||
isUrlCopied,
|
||||
copyUrlToClipboard
|
||||
}: Props) => (
|
||||
<div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2">
|
||||
{isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>}
|
||||
{!isLoading && !decryptedSecret && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||
<EmptyState title="Secret has either expired or does not exist!" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{!isLoading && decryptedSecret && (
|
||||
<div className="dark relative flex h-full w-full items-center overflow-y-auto border border-mineshaft-700 bg-mineshaft-900 rounded-md p-2 md:p-3 pr-2">
|
||||
<div className="thin-scrollbar flex h-full max-h-44 w-full flex-1 overflow-y-scroll break-words pr-4 dark:[color-scheme:dark]">
|
||||
<div className="align-center flex w-full min-w-full whitespace-pre-line">
|
||||
{decryptedSecret}
|
||||
}: Props) => {
|
||||
const [isVisible, setIsVisible] = useToggle(false);
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2">
|
||||
{isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>}
|
||||
{!isLoading && !decryptedSecret && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||
<EmptyState title="Secret has either expired or does not exist!" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{!isLoading && decryptedSecret && (
|
||||
<div className="dark relative flex h-full w-full items-center overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-2 pr-2 md:p-3">
|
||||
<div
|
||||
className={`thin-scrollbar flex h-full max-h-44 w-full flex-1 overflow-y-scroll ${
|
||||
isVisible ? "break-words" : "break-all"
|
||||
} pr-4 dark:[color-scheme:dark]`}
|
||||
>
|
||||
<div className="align-center flex w-full min-w-full whitespace-pre-line">
|
||||
{isVisible ? decryptedSecret : replaceContentWithDot(decryptedSecret)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-1 right-0 mx-1 flex max-h-8 sm:top-2 sm:right-5">
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
colorSchema="primary"
|
||||
ariaLabel="copy to clipboard"
|
||||
onClick={copyUrlToClipboard}
|
||||
className="mr-1 flex max-h-8 items-center rounded"
|
||||
size="xs"
|
||||
>
|
||||
<FontAwesomeIcon className="pr-2" icon={isUrlCopied ? faCheck : faCopy} /> Copy
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
colorSchema="primary"
|
||||
ariaLabel="toggle visibility"
|
||||
onClick={() => setIsVisible.toggle()}
|
||||
className="flex max-h-8 items-center rounded"
|
||||
size="xs"
|
||||
>
|
||||
<FontAwesomeIcon icon={isVisible ? faEyeSlash : faEye} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
colorSchema="primary"
|
||||
ariaLabel="copy to clipboard"
|
||||
onClick={copyUrlToClipboard}
|
||||
className="mx-1 flex max-h-8 items-center rounded absolute top-1 sm:top-2 right-0 sm:right-5"
|
||||
size="xs"
|
||||
>
|
||||
<FontAwesomeIcon className="pr-2" icon={isUrlCopied ? faCheck : faCopy} /> Copy
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -16,9 +16,10 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
Tr,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { useUser } from "@app/context";
|
||||
import { useSubscription, useUser } from "@app/context";
|
||||
import { useDebounce, usePopUp } from "@app/hooks";
|
||||
import { useAdminDeleteUser, useAdminGetUsers } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
@ -27,8 +28,8 @@ const UserPanelTable = ({
|
||||
handlePopUpOpen
|
||||
}: {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["removeUser"]>,
|
||||
data: {
|
||||
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan"]>,
|
||||
data?: {
|
||||
username: string;
|
||||
id: string;
|
||||
}
|
||||
@ -38,6 +39,7 @@ const UserPanelTable = ({
|
||||
const { user } = useUser();
|
||||
const userId = user?.id || "";
|
||||
const debounedSearchTerm = useDebounce(searchUserFilter, 500);
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useAdminGetUsers({
|
||||
limit: 20,
|
||||
@ -83,7 +85,13 @@ const UserPanelTable = ({
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
isDisabled={userId === id}
|
||||
onClick={() => handlePopUpOpen("removeUser", { username, id })}
|
||||
onClick={() => {
|
||||
if (!subscription?.instanceUserManagement) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
handlePopUpOpen("removeUser", { username, id });
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
@ -117,7 +125,8 @@ const UserPanelTable = ({
|
||||
|
||||
export const UserPanel = () => {
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"removeUser"
|
||||
"removeUser",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync: deleteUser } = useAdminDeleteUser();
|
||||
@ -156,6 +165,11 @@ export const UserPanel = () => {
|
||||
onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)}
|
||||
onDeleteApproved={handleRemoveUser}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="Deleting users via Admin UI is only available on Infisical's Pro plan and above."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user