mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 23:27:14 +00:00
Finish basic CRUD groups
This commit is contained in:
Vendored
+2
@@ -3,6 +3,7 @@ import "fastify";
|
|||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -85,6 +86,7 @@ declare module "fastify" {
|
|||||||
orgRole: TOrgRoleServiceFactory;
|
orgRole: TOrgRoleServiceFactory;
|
||||||
superAdmin: TSuperAdminServiceFactory;
|
superAdmin: TSuperAdminServiceFactory;
|
||||||
user: TUserServiceFactory;
|
user: TUserServiceFactory;
|
||||||
|
group: TGroupServiceFactory;
|
||||||
apiKey: TApiKeyServiceFactory;
|
apiKey: TApiKeyServiceFactory;
|
||||||
project: TProjectServiceFactory;
|
project: TProjectServiceFactory;
|
||||||
projectMembership: TProjectMembershipServiceFactory;
|
projectMembership: TProjectMembershipServiceFactory;
|
||||||
|
|||||||
Vendored
+4
@@ -23,6 +23,9 @@ import {
|
|||||||
TGitAppOrg,
|
TGitAppOrg,
|
||||||
TGitAppOrgInsert,
|
TGitAppOrgInsert,
|
||||||
TGitAppOrgUpdate,
|
TGitAppOrgUpdate,
|
||||||
|
TGroups,
|
||||||
|
TGroupsInsert,
|
||||||
|
TGroupsUpdate,
|
||||||
TIdentities,
|
TIdentities,
|
||||||
TIdentitiesInsert,
|
TIdentitiesInsert,
|
||||||
TIdentitiesUpdate,
|
TIdentitiesUpdate,
|
||||||
@@ -187,6 +190,7 @@ import {
|
|||||||
declare module "knex/types/tables" {
|
declare module "knex/types/tables" {
|
||||||
interface Tables {
|
interface Tables {
|
||||||
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
||||||
|
[TableName.Groups]: Knex.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
|
||||||
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
||||||
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.Groups))) {
|
||||||
|
await knex.schema.createTable(TableName.Groups, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.string("slug").notNullable();
|
||||||
|
t.unique(["orgId", "slug"]);
|
||||||
|
t.string("role").notNullable();
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.Groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.Groups);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.Groups);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const GroupsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
roleId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGroups = z.infer<typeof GroupsSchema>;
|
||||||
|
export type TGroupsInsert = Omit<z.input<typeof GroupsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TGroupsUpdate = Partial<Omit<z.input<typeof GroupsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -5,6 +5,7 @@ export * from "./auth-tokens";
|
|||||||
export * from "./backup-private-key";
|
export * from "./backup-private-key";
|
||||||
export * from "./git-app-install-sessions";
|
export * from "./git-app-install-sessions";
|
||||||
export * from "./git-app-org";
|
export * from "./git-app-org";
|
||||||
|
export * from "./groups";
|
||||||
export * from "./identities";
|
export * from "./identities";
|
||||||
export * from "./identity-access-tokens";
|
export * from "./identity-access-tokens";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
|
Groups = "groups",
|
||||||
UserAliases = "user_aliases",
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
AuthTokens = "auth_tokens",
|
AuthTokens = "auth_tokens",
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { GroupsSchema, OrgMembershipRole } from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
slug: z.string().trim().min(1),
|
||||||
|
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess) // TODO: add describe
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.createGroup({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.body.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:currentSlug",
|
||||||
|
method: "PATCH",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
currentSlug: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
slug: z.string().trim().min(1),
|
||||||
|
role: z.string().trim().min(1)
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.updateGroup({
|
||||||
|
currentSlug: req.params.currentSlug,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.permission.orgId as string, // note
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:slug",
|
||||||
|
method: "DELETE",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
slug: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.deleteGroup({
|
||||||
|
slug: req.params.slug,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.permission.orgId as string, // note
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { registerGroupRouter } from "./group-router";
|
||||||
import { registerLdapRouter } from "./ldap-router";
|
import { registerLdapRouter } from "./ldap-router";
|
||||||
import { registerLicenseRouter } from "./license-router";
|
import { registerLicenseRouter } from "./license-router";
|
||||||
import { registerOrgRoleRouter } from "./org-role-router";
|
import { registerOrgRoleRouter } from "./org-role-router";
|
||||||
@@ -40,4 +41,5 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
||||||
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
||||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||||
|
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
|
||||||
|
|
||||||
|
export const groupDALFactory = (db: TDbClient) => {
|
||||||
|
const groupOrm = ormify(db, TableName.Groups);
|
||||||
|
|
||||||
|
return { ...groupOrm };
|
||||||
|
};
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TGroupDALFactory } from "./group-dal";
|
||||||
|
import { TCreateGroupDTO, TDeleteGroupDTO, TUpdateGroupDTO } from "./group-types";
|
||||||
|
|
||||||
|
type TGroupServiceFactoryDep = {
|
||||||
|
groupDAL: TGroupDALFactory;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
||||||
|
|
||||||
|
export const groupServiceFactory = ({ groupDAL, permissionService, licenseService }: TGroupServiceFactoryDep) => {
|
||||||
|
const createGroup = async ({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TCreateGroupDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
||||||
|
role,
|
||||||
|
orgId
|
||||||
|
);
|
||||||
|
const isCustomRole = Boolean(customRole);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
|
||||||
|
const group = await groupDAL.create({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
orgId,
|
||||||
|
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGroup = async ({
|
||||||
|
currentSlug,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateGroupDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update group due to plan restrictio Upgrade plan to update group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await groupDAL.findOne({ orgId, slug: currentSlug });
|
||||||
|
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${currentSlug}` });
|
||||||
|
|
||||||
|
let customRole: TOrgRoles | undefined;
|
||||||
|
if (role) {
|
||||||
|
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
|
||||||
|
role,
|
||||||
|
group.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const isCustomRole = Boolean(customOrgRole);
|
||||||
|
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasRequiredNewRolePermission)
|
||||||
|
throw new BadRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updatedGroup] = await groupDAL.update(
|
||||||
|
{
|
||||||
|
orgId,
|
||||||
|
slug: currentSlug
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
...(role
|
||||||
|
? {
|
||||||
|
role: customRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGroup = async ({ slug, actor, actorId, orgId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to delete group due to plan restriction. Upgrade plan to delete group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const [group] = await groupDAL.delete({
|
||||||
|
orgId,
|
||||||
|
slug
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createGroup,
|
||||||
|
updateGroup,
|
||||||
|
deleteGroup
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateGroupDTO = {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
role: string;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TUpdateGroupDTO = {
|
||||||
|
currentSlug: string;
|
||||||
|
} & Partial<{
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
role: string;
|
||||||
|
}> &
|
||||||
|
TOrgPermission;
|
||||||
|
|
||||||
|
export type TDeleteGroupDTO = {
|
||||||
|
slug: string;
|
||||||
|
} & TOrgPermission;
|
||||||
@@ -20,6 +20,7 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
groups: true,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
groups: true,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export type TFeatureSet = {
|
|||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
ldap: false;
|
ldap: false;
|
||||||
|
groups: true;
|
||||||
status: null;
|
status: null;
|
||||||
trial_end: null;
|
trial_end: null;
|
||||||
has_used_trial: true;
|
has_used_trial: true;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
Ldap = "ldap",
|
Ldap = "ldap",
|
||||||
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity"
|
Identity = "identity"
|
||||||
@@ -33,6 +34,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||||
@@ -83,6 +85,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Groups);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { registerV1EERoutes } from "@app/ee/routes/v1";
|
|||||||
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
|
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
|
import { groupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||||
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
|
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
|
||||||
@@ -194,6 +196,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
|
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
|
||||||
const gitAppOrgDAL = gitAppDALFactory(db);
|
const gitAppOrgDAL = gitAppDALFactory(db);
|
||||||
|
const groupDAL = groupDALFactory(db);
|
||||||
const secretScanningDAL = secretScanningDALFactory(db);
|
const secretScanningDAL = secretScanningDALFactory(db);
|
||||||
const licenseDAL = licenseDALFactory(db);
|
const licenseDAL = licenseDALFactory(db);
|
||||||
|
|
||||||
@@ -234,6 +237,11 @@ export const registerRoutes = async (
|
|||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
const groupService = groupServiceFactory({
|
||||||
|
groupDAL,
|
||||||
|
permissionService,
|
||||||
|
licenseService
|
||||||
|
});
|
||||||
const scimService = scimServiceFactory({
|
const scimService = scimServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
scimDAL,
|
scimDAL,
|
||||||
@@ -287,6 +295,7 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
|
groupDAL,
|
||||||
orgBotDAL
|
orgBotDAL
|
||||||
});
|
});
|
||||||
const signupService = authSignupServiceFactory({
|
const signupService = authSignupServiceFactory({
|
||||||
@@ -564,6 +573,7 @@ export const registerRoutes = async (
|
|||||||
password: passwordService,
|
password: passwordService,
|
||||||
signup: signupService,
|
signup: signupService,
|
||||||
user: userService,
|
user: userService,
|
||||||
|
group: groupService,
|
||||||
permission: permissionService,
|
permission: permissionService,
|
||||||
org: orgService,
|
org: orgService,
|
||||||
orgRole: orgRoleService,
|
orgRole: orgRoleService,
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IncidentContactsSchema, OrganizationsSchema, OrgMembershipsSchema, UsersSchema } from "@app/db/schemas";
|
import {
|
||||||
|
GroupsSchema,
|
||||||
|
IncidentContactsSchema,
|
||||||
|
OrganizationsSchema,
|
||||||
|
OrgMembershipsSchema,
|
||||||
|
UsersSchema
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -196,4 +202,31 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { incidentContactsOrg };
|
return { incidentContactsOrg };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/groups",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
groups: GroupsSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const groups = await server.services.org.getOrgGroups({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.permission.orgId as string,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { groups };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
||||||
import { TProjects } from "@app/db/schemas/projects";
|
import { TProjects } from "@app/db/schemas/projects";
|
||||||
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -34,6 +35,7 @@ import {
|
|||||||
TDeleteOrgMembershipDTO,
|
TDeleteOrgMembershipDTO,
|
||||||
TFindAllWorkspacesDTO,
|
TFindAllWorkspacesDTO,
|
||||||
TFindOrgMembersByEmailDTO,
|
TFindOrgMembersByEmailDTO,
|
||||||
|
TGetOrgGroupsDTO,
|
||||||
TInviteUserToOrgDTO,
|
TInviteUserToOrgDTO,
|
||||||
TUpdateOrgDTO,
|
TUpdateOrgDTO,
|
||||||
TUpdateOrgMembershipDTO,
|
TUpdateOrgMembershipDTO,
|
||||||
@@ -45,6 +47,7 @@ type TOrgServiceFactoryDep = {
|
|||||||
orgBotDAL: TOrgBotDALFactory;
|
orgBotDAL: TOrgBotDALFactory;
|
||||||
orgRoleDAL: TOrgRoleDALFactory;
|
orgRoleDAL: TOrgRoleDALFactory;
|
||||||
userDAL: TUserDALFactory;
|
userDAL: TUserDALFactory;
|
||||||
|
groupDAL: TGroupDALFactory;
|
||||||
projectDAL: TProjectDALFactory;
|
projectDAL: TProjectDALFactory;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId" | "delete">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId" | "delete">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
||||||
@@ -64,6 +67,7 @@ export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
|||||||
export const orgServiceFactory = ({
|
export const orgServiceFactory = ({
|
||||||
orgDAL,
|
orgDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
|
groupDAL,
|
||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
incidentContactDAL,
|
incidentContactDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -113,6 +117,12 @@ export const orgServiceFactory = ({
|
|||||||
return members;
|
return members;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getOrgGroups = async ({ actor, actorId, orgId, actorAuthMethod, actorOrgId }: TGetOrgGroupsDTO) => {
|
||||||
|
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
const groups = await groupDAL.find({ orgId });
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
|
||||||
const findOrgMembersByUsername = async ({
|
const findOrgMembersByUsername = async ({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -674,6 +684,7 @@ export const orgServiceFactory = ({
|
|||||||
// incident contacts
|
// incident contacts
|
||||||
findIncidentContacts,
|
findIncidentContacts,
|
||||||
createIncidentContact,
|
createIncidentContact,
|
||||||
deleteIncidentContact
|
deleteIncidentContact,
|
||||||
|
getOrgGroups
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,3 +53,5 @@ export type TFindAllWorkspacesDTO = {
|
|||||||
export type TUpdateOrgDTO = {
|
export type TUpdateOrgDTO = {
|
||||||
data: Partial<{ name: string; slug: string; authEnforced: boolean; scimEnabled: boolean }>;
|
data: Partial<{ name: string; slug: string; authEnforced: boolean; scimEnabled: boolean }>;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TGetOrgGroupsDTO = TOrgPermission;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Ldap = "ldap",
|
Ldap = "ldap",
|
||||||
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity"
|
Identity = "identity"
|
||||||
@@ -31,6 +32,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export {
|
||||||
|
useCreateGroup,
|
||||||
|
useDeleteGroup,
|
||||||
|
useUpdateGroup} from "./mutations";
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { organizationKeys } from "../organization/queries";
|
||||||
|
import { TGroup } from "./types";
|
||||||
|
|
||||||
|
export const useCreateGroup = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
organizationId,
|
||||||
|
role
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
organizationId: string;
|
||||||
|
role?: string;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: group
|
||||||
|
} = await apiRequest.post<TGroup>("/api/v1/groups", {
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
organizationId,
|
||||||
|
role
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { organizationId }) => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getOrgGroups(organizationId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateGroup = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
currentSlug,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role
|
||||||
|
}: {
|
||||||
|
currentSlug: string;
|
||||||
|
name?: string;
|
||||||
|
slug?: string;
|
||||||
|
role?: string;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: group
|
||||||
|
} = await apiRequest.patch<TGroup>(`/api/v1/groups/${currentSlug}`, {
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
},
|
||||||
|
onSuccess: ({ orgId }) => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getOrgGroups(orgId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteGroup = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
slug
|
||||||
|
}: {
|
||||||
|
slug: string;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: group
|
||||||
|
} = await apiRequest.delete<TGroup>(`/api/v1/groups/${slug}`);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
},
|
||||||
|
onSuccess: ({ orgId }) => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getOrgGroups(orgId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export type TGroup = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
orgId: string;
|
||||||
|
createAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
role: string;
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ export * from "./apiKeys";
|
|||||||
export * from "./auditLogs";
|
export * from "./auditLogs";
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./bots";
|
export * from "./bots";
|
||||||
|
export * from "./groups";
|
||||||
export * from "./identities";
|
export * from "./identities";
|
||||||
export * from "./incidentContacts";
|
export * from "./incidentContacts";
|
||||||
export * from "./integrationAuth";
|
export * from "./integrationAuth";
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
useDeleteOrgPmtMethod,
|
useDeleteOrgPmtMethod,
|
||||||
useDeleteOrgTaxId,
|
useDeleteOrgTaxId,
|
||||||
useGetIdentityMembershipOrgs,
|
useGetIdentityMembershipOrgs,
|
||||||
|
useGetOrganizationGroups,
|
||||||
useGetOrganizations,
|
useGetOrganizations,
|
||||||
useGetOrgBillingDetails,
|
useGetOrgBillingDetails,
|
||||||
useGetOrgInvoices,
|
useGetOrgInvoices,
|
||||||
@@ -17,6 +18,5 @@ export {
|
|||||||
useGetOrgPmtMethods,
|
useGetOrgPmtMethods,
|
||||||
useGetOrgTaxIds,
|
useGetOrgTaxIds,
|
||||||
useGetOrgTrialUrl,
|
useGetOrgTrialUrl,
|
||||||
useUpdateOrg,
|
useUpdateOrg,
|
||||||
useUpdateOrgBillingDetails
|
useUpdateOrgBillingDetails} from "./queries";
|
||||||
} from "./queries";
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TGroup } from "../groups/types";
|
||||||
import { IdentityMembershipOrg } from "../identities/types";
|
import { IdentityMembershipOrg } from "../identities/types";
|
||||||
import {
|
import {
|
||||||
BillingDetails,
|
BillingDetails,
|
||||||
@@ -27,8 +28,8 @@ export const organizationKeys = {
|
|||||||
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
||||||
getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const,
|
getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const,
|
||||||
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const,
|
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const,
|
||||||
getOrgIdentityMemberships: (orgId: string) =>
|
getOrgIdentityMemberships: (orgId: string) => [{ orgId }, "organization-identity-memberships"] as const,
|
||||||
[{ orgId }, "organization-identity-memberships"] as const
|
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchOrganizations = async () => {
|
export const fetchOrganizations = async () => {
|
||||||
@@ -404,3 +405,16 @@ export const useDeleteOrgById = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetOrganizationGroups = (organizationId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: organizationKeys.getOrgGroups(organizationId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { groups }
|
||||||
|
} = await apiRequest.get<{ groups: TGroup[] }>(`/api/v1/organization/${organizationId}/groups`);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -20,6 +20,7 @@ export type SubscriptionPlan = {
|
|||||||
samlSSO: boolean;
|
samlSSO: boolean;
|
||||||
scim: boolean;
|
scim: boolean;
|
||||||
ldap: boolean;
|
ldap: boolean;
|
||||||
|
groups: boolean;
|
||||||
status:
|
status:
|
||||||
| "incomplete"
|
| "incomplete"
|
||||||
| "incomplete_expired"
|
| "incomplete_expired"
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
|||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
import { OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components";
|
import {
|
||||||
|
OrgGroupsTab,
|
||||||
|
OrgIdentityTab,
|
||||||
|
OrgMembersTab,
|
||||||
|
OrgRoleTabSection
|
||||||
|
} from "./components";
|
||||||
|
|
||||||
enum TabSections {
|
enum TabSections {
|
||||||
Member = "members",
|
Member = "members",
|
||||||
|
Groups = "groups",
|
||||||
Roles = "roles",
|
Roles = "roles",
|
||||||
Identities = "identities"
|
Identities = "identities"
|
||||||
}
|
}
|
||||||
@@ -20,6 +26,7 @@ export const MembersPage = withPermission(
|
|||||||
<Tabs defaultValue={TabSections.Member}>
|
<Tabs defaultValue={TabSections.Member}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Member}>People</Tab>
|
<Tab value={TabSections.Member}>People</Tab>
|
||||||
|
<Tab value={TabSections.Groups}>Groups</Tab>
|
||||||
<Tab value={TabSections.Identities}>
|
<Tab value={TabSections.Identities}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<p>Machine Identities</p>
|
<p>Machine Identities</p>
|
||||||
@@ -33,6 +40,9 @@ export const MembersPage = withPermission(
|
|||||||
<TabPanel value={TabSections.Member}>
|
<TabPanel value={TabSections.Member}>
|
||||||
<OrgMembersTab />
|
<OrgMembersTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Groups}>
|
||||||
|
<OrgGroupsTab />
|
||||||
|
</TabPanel>
|
||||||
<TabPanel value={TabSections.Identities}>
|
<TabPanel value={TabSections.Identities}>
|
||||||
<OrgIdentityTab />
|
<OrgIdentityTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
import { OrgGroupsSection } from "./components";
|
||||||
|
|
||||||
|
export const OrgGroupsTab = () => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key="panel-org-groups"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<OrgGroupsSection />
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { Button, FormControl, Input, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
||||||
|
import { useOrganization } from "@app/context";
|
||||||
|
import {
|
||||||
|
useCreateGroup,
|
||||||
|
useGetOrgRoles,
|
||||||
|
useUpdateGroup
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
const GroupFormSchema = z.object({
|
||||||
|
name: z.string().min(1, "Name cannot be empty").max(50, "Name must be 50 characters or fewer"),
|
||||||
|
slug: z.string().min(1, "Slug cannot be empty").max(36, "Slug must be 36 characters or fewer"),
|
||||||
|
role: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGroupFormData = z.infer<typeof GroupFormSchema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popUp: UsePopUpState<["group"]>;
|
||||||
|
handlePopUpClose: (popUpName: keyof UsePopUpState<["group"]>) => void;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["group"]>, state?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrgGroupModal = ({
|
||||||
|
popUp,
|
||||||
|
handlePopUpClose,
|
||||||
|
handlePopUpToggle
|
||||||
|
}: Props) => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const { data: roles } = useGetOrgRoles(currentOrg?.id || "");
|
||||||
|
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateGroup();
|
||||||
|
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateGroup();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
} = useForm<TGroupFormData>({
|
||||||
|
resolver: zodResolver(GroupFormSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const group = popUp?.group?.data as {
|
||||||
|
groupId: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
role: string;
|
||||||
|
customRole: {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!roles?.length) return;
|
||||||
|
|
||||||
|
if (group) {
|
||||||
|
reset({
|
||||||
|
name: group.name,
|
||||||
|
slug: group.slug,
|
||||||
|
role: group?.customRole?.slug ?? group.role
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
slug: "",
|
||||||
|
role: roles[0].slug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [popUp?.group?.data, roles]);
|
||||||
|
|
||||||
|
const onGroupModalSubmit = async ({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role
|
||||||
|
}: TGroupFormData) => {
|
||||||
|
try {
|
||||||
|
if (!currentOrg?.id) return;
|
||||||
|
|
||||||
|
const group = popUp?.group?.data as {
|
||||||
|
groupId: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (group) {
|
||||||
|
await updateMutateAsync({
|
||||||
|
currentSlug: group.slug,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role: role || undefined
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createMutateAsync({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
organizationId: currentOrg.id,
|
||||||
|
role: role || undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handlePopUpToggle("group", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: `Failed to ${popUp?.group?.data ? "updated" : "created"} group`,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.group?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
handlePopUpToggle("group", isOpen);
|
||||||
|
reset();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title={`${popUp?.group?.data ? "Update" : "Create"} Group`}
|
||||||
|
subTitle="Manage users more easily with groups"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(onGroupModalSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Name"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="Engineering"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="slug"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Slug"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="engineering"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="role"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={`${popUp?.group?.data ? "Update" : ""} Role`}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{(roles || []).map(({ name, slug }) => (
|
||||||
|
<SelectItem value={slug} key={`org-group-role-${slug}`}>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={createIsLoading || updateIsLoading}
|
||||||
|
>
|
||||||
|
{!popUp?.group?.data ? "Create" : "Update"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpClose("group")}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
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";
|
||||||
|
|
||||||
|
import { OrgGroupModal } from "./OrgGroupModal";
|
||||||
|
import { OrgGroupsTable } from "./OrgGroupsTable";
|
||||||
|
|
||||||
|
export const OrgGroupsSection = () => {
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = 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 onDeleteGroupSubmit = async ({
|
||||||
|
name,
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
// import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { faMagnifyingGlass, faPencil, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
// Button,
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
// Select,
|
||||||
|
// SelectItem,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization} from "@app/context";
|
||||||
|
import {
|
||||||
|
useGetOrganizationGroups,
|
||||||
|
// useGetOrgRoles
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<
|
||||||
|
["group", "deleteGroup"]
|
||||||
|
>,
|
||||||
|
data?: {
|
||||||
|
groupId?: string;
|
||||||
|
name?: string;
|
||||||
|
slug?: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrgGroupsTable = ({
|
||||||
|
handlePopUpOpen
|
||||||
|
}: Props) => {
|
||||||
|
// const { createNotification } = useNotificationContext();
|
||||||
|
const [searchGroupsFilter, setSearchGroupsFilter] = useState("");
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const orgId = currentOrg?.id || "";
|
||||||
|
const { isLoading, data: groups } = useGetOrganizationGroups(orgId);
|
||||||
|
|
||||||
|
// const { data: roles } = useGetOrgRoles(orgId);
|
||||||
|
|
||||||
|
console.log("OrgGroupsTable groups: ", groups);
|
||||||
|
console.log("OrgGroupsTable roles: ", groups);
|
||||||
|
|
||||||
|
// const handleChangeRole = ({
|
||||||
|
// groupId,
|
||||||
|
// role
|
||||||
|
// }: {
|
||||||
|
// groupId: string;
|
||||||
|
// role: string;
|
||||||
|
// }) => {
|
||||||
|
// try {
|
||||||
|
|
||||||
|
// // TODO
|
||||||
|
|
||||||
|
// 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 }) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`org-group-${id}`}>
|
||||||
|
<Td>{name}</Td>
|
||||||
|
<Td>{slug}</Td>
|
||||||
|
<Td>N/A</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center justify-end">
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgPermissionActions.Edit}
|
||||||
|
a={OrgPermissionSubjects.Groups}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Tooltip content="Edit group">
|
||||||
|
<IconButton
|
||||||
|
onClick={async () => {
|
||||||
|
handlePopUpOpen("group", {
|
||||||
|
groupId: id,
|
||||||
|
name,
|
||||||
|
slug
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPencil} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Groups}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Tooltip content="Delete group">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
console.log("Delete group");
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { OrgGroupsSection } from "./OrgGroupsSection";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { OrgGroupsSection } from "./OrgGroupsSection";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { OrgGroupsTab } from "./OrgGroupsTab";
|
||||||
+1
-1
@@ -140,7 +140,7 @@ export const IdentityModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Pro
|
|||||||
const error = err as any;
|
const error = err as any;
|
||||||
const text =
|
const text =
|
||||||
error?.response?.data?.message ??
|
error?.response?.data?.message ??
|
||||||
`Failed to ${popUp?.identity?.data ? "updated" : "created"} identity`;
|
`Failed to ${popUp?.identity?.data ? "update" : "create"} identity`;
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text,
|
text,
|
||||||
|
|||||||
+3
@@ -52,6 +52,9 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
const { data, isLoading } = useGetIdentityMembershipOrgs(orgId);
|
const { data, isLoading } = useGetIdentityMembershipOrgs(orgId);
|
||||||
|
|
||||||
const { data: roles } = useGetOrgRoles(orgId);
|
const { data: roles } = useGetOrgRoles(orgId);
|
||||||
|
|
||||||
|
console.log("IdentityTable data: ", data);
|
||||||
|
console.log("IdentityTable roles: ", roles);
|
||||||
|
|
||||||
const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => {
|
const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { OrgMembersSection } from "./components";
|
|||||||
export const OrgMembersTab = () => {
|
export const OrgMembersTab = () => {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="panel-service-token"
|
key="panel-org-members"
|
||||||
transition={{ duration: 0.15 }}
|
transition={{ duration: 0.15 }}
|
||||||
initial={{ opacity: 0, translateX: 30 }}
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
animate={{ opacity: 1, translateX: 0 }}
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
|||||||
+3
-1
@@ -62,6 +62,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
|||||||
|
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId);
|
const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId);
|
||||||
|
|
||||||
|
console.log("OrgGroupsTable members: ", members);
|
||||||
|
|
||||||
const { mutateAsync: addUserMutateAsync } = useAddUserToOrg();
|
const { mutateAsync: addUserMutateAsync } = useAddUserToOrg();
|
||||||
const { mutateAsync: updateUserOrgRole } = useUpdateOrgUserRole();
|
const { mutateAsync: updateUserOrgRole } = useUpdateOrgUserRole();
|
||||||
@@ -263,7 +265,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
|||||||
</TBody>
|
</TBody>
|
||||||
</Table>
|
</Table>
|
||||||
{!isLoading && filterdUser?.length === 0 && (
|
{!isLoading && filterdUser?.length === 0 && (
|
||||||
<EmptyState title="No project members found" icon={faUsers} />
|
<EmptyState title="No organization members found" icon={faUsers} />
|
||||||
)}
|
)}
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export { OrgGroupsTab } from "./OrgGroupsTab";
|
||||||
export { OrgIdentityTab } from "./OrgIdentityTab";
|
export { OrgIdentityTab } from "./OrgIdentityTab";
|
||||||
export { OrgMembersTab } from "./OrgMembersTab";
|
export { OrgMembersTab } from "./OrgMembersTab";
|
||||||
export { OrgRoleTabSection } from "./OrgRoleTabSection";
|
export { OrgRoleTabSection } from "./OrgRoleTabSection";
|
||||||
Reference in New Issue
Block a user