mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge remote-tracking branch 'origin' into project-role-concept
This commit is contained in:
@@ -107,7 +107,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -425,6 +425,21 @@ export const PROJECTS = {
|
|||||||
},
|
},
|
||||||
LIST_INTEGRATION_AUTHORIZATION: {
|
LIST_INTEGRATION_AUTHORIZATION: {
|
||||||
workspaceId: "The ID of the project to list integration auths for."
|
workspaceId: "The ID of the project to list integration auths for."
|
||||||
|
},
|
||||||
|
LIST_CAS: {
|
||||||
|
slug: "The slug of the project to list CAs for.",
|
||||||
|
status: "The status of the CA to filter by.",
|
||||||
|
friendlyName: "The friendly name of the CA to filter by.",
|
||||||
|
commonName: "The common name of the CA to filter by.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th CA.",
|
||||||
|
limit: "The number of CAs to return."
|
||||||
|
},
|
||||||
|
LIST_CERTIFICATES: {
|
||||||
|
slug: "The slug of the project to list certificates for.",
|
||||||
|
friendlyName: "The friendly name of the certificate to filter by.",
|
||||||
|
commonName: "The common name of the certificate to filter by.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
||||||
|
limit: "The number of certificates to return."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -317,10 +317,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe("The slug of the project to list CAs.")
|
slug: slugSchema.describe(PROJECTS.LIST_CAS.slug)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional()
|
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status),
|
||||||
|
friendlyName: z.string().optional().describe(PROJECTS.LIST_CAS.friendlyName),
|
||||||
|
commonName: z.string().optional().describe(PROJECTS.LIST_CAS.commonName),
|
||||||
|
offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CAS.offset),
|
||||||
|
limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CAS.limit)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -336,11 +340,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
orgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
type: ProjectFilterType.SLUG
|
type: ProjectFilterType.SLUG
|
||||||
},
|
},
|
||||||
status: req.query.status,
|
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type
|
actor: req.permission.type,
|
||||||
|
...req.query
|
||||||
});
|
});
|
||||||
return { cas };
|
return { cas };
|
||||||
}
|
}
|
||||||
@@ -354,11 +358,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe("The slug of the project to list certificates.")
|
slug: slugSchema.describe(PROJECTS.LIST_CERTIFICATES.slug)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
offset: z.coerce.number().min(0).max(100).default(0),
|
friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName),
|
||||||
limit: z.coerce.number().min(1).max(100).default(25)
|
commonName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.commonName),
|
||||||
|
offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset),
|
||||||
|
limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -8,19 +8,35 @@ export type TCertificateDALFactory = ReturnType<typeof certificateDALFactory>;
|
|||||||
export const certificateDALFactory = (db: TDbClient) => {
|
export const certificateDALFactory = (db: TDbClient) => {
|
||||||
const certificateOrm = ormify(db, TableName.Certificate);
|
const certificateOrm = ormify(db, TableName.Certificate);
|
||||||
|
|
||||||
const countCertificatesInProject = async (projectId: string) => {
|
const countCertificatesInProject = async ({
|
||||||
|
projectId,
|
||||||
|
friendlyName,
|
||||||
|
commonName
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
friendlyName?: string;
|
||||||
|
commonName?: string;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
interface CountResult {
|
interface CountResult {
|
||||||
count: string;
|
count: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = await db
|
let query = db
|
||||||
.replicaNode()(TableName.Certificate)
|
.replicaNode()(TableName.Certificate)
|
||||||
.join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`)
|
.join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`)
|
||||||
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
|
||||||
.where(`${TableName.Project}.id`, projectId)
|
.where(`${TableName.Project}.id`, projectId);
|
||||||
.count("*")
|
|
||||||
.first();
|
if (friendlyName) {
|
||||||
|
query = query.andWhere(`${TableName.Certificate}.friendlyName`, friendlyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commonName) {
|
||||||
|
query = query.andWhere(`${TableName.Certificate}.commonName`, commonName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = await query.count("*").first();
|
||||||
|
|
||||||
return parseInt((count as unknown as CountResult).count || "0", 10);
|
return parseInt((count as unknown as CountResult).count || "0", 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -575,6 +575,10 @@ export const projectServiceFactory = ({
|
|||||||
*/
|
*/
|
||||||
const listProjectCas = async ({
|
const listProjectCas = async ({
|
||||||
status,
|
status,
|
||||||
|
friendlyName,
|
||||||
|
commonName,
|
||||||
|
limit = 25,
|
||||||
|
offset = 0,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
@@ -596,10 +600,15 @@ export const projectServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
const cas = await certificateAuthorityDAL.find({
|
const cas = await certificateAuthorityDAL.find(
|
||||||
projectId: project.id,
|
{
|
||||||
...(status && { status })
|
projectId: project.id,
|
||||||
});
|
...(status && { status }),
|
||||||
|
...(friendlyName && { friendlyName }),
|
||||||
|
...(commonName && { commonName })
|
||||||
|
},
|
||||||
|
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
||||||
|
);
|
||||||
|
|
||||||
return cas;
|
return cas;
|
||||||
};
|
};
|
||||||
@@ -608,8 +617,10 @@ export const projectServiceFactory = ({
|
|||||||
* Return list of certificates for project
|
* Return list of certificates for project
|
||||||
*/
|
*/
|
||||||
const listProjectCertificates = async ({
|
const listProjectCertificates = async ({
|
||||||
offset,
|
limit = 25,
|
||||||
limit,
|
offset = 0,
|
||||||
|
friendlyName,
|
||||||
|
commonName,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
@@ -634,12 +645,18 @@ export const projectServiceFactory = ({
|
|||||||
{
|
{
|
||||||
$in: {
|
$in: {
|
||||||
caId: cas.map((ca) => ca.id)
|
caId: cas.map((ca) => ca.id)
|
||||||
}
|
},
|
||||||
|
...(friendlyName && { friendlyName }),
|
||||||
|
...(commonName && { commonName })
|
||||||
},
|
},
|
||||||
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
||||||
);
|
);
|
||||||
|
|
||||||
const count = await certificateDAL.countCertificatesInProject(project.id);
|
const count = await certificateDAL.countCertificatesInProject({
|
||||||
|
projectId: project.id,
|
||||||
|
friendlyName,
|
||||||
|
commonName
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificates,
|
certificates,
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ export type AddUserToWsDTO = {
|
|||||||
|
|
||||||
export type TListProjectCasDTO = {
|
export type TListProjectCasDTO = {
|
||||||
status?: CaStatus;
|
status?: CaStatus;
|
||||||
|
friendlyName?: string;
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
commonName?: string;
|
||||||
filter: Filter;
|
filter: Filter;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
@@ -96,4 +100,6 @@ export type TListProjectCertsDTO = {
|
|||||||
filter: Filter;
|
filter: Filter;
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
friendlyName?: string;
|
||||||
|
commonName?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
"github.com/Infisical/infisical-merge/packages/models"
|
"github.com/Infisical/infisical-merge/packages/models"
|
||||||
"github.com/Infisical/infisical-merge/packages/srp"
|
"github.com/Infisical/infisical-merge/packages/srp"
|
||||||
"github.com/Infisical/infisical-merge/packages/util"
|
"github.com/Infisical/infisical-merge/packages/util"
|
||||||
"github.com/chzyer/readline"
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
@@ -205,6 +204,7 @@ var loginCmd = &cobra.Command{
|
|||||||
if !overrideDomain {
|
if !overrideDomain {
|
||||||
domainQuery = false
|
domainQuery = false
|
||||||
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE)
|
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE)
|
||||||
|
config.INFISICAL_LOGIN_URL = fmt.Sprintf("%s/login", strings.TrimSuffix(config.INFISICAL_URL, "/api"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -713,7 +713,7 @@ func askForMFACode() string {
|
|||||||
return mfaVerifyCode
|
return mfaVerifyCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func askToPasteJwtToken(stdin *readline.CancelableStdin, success chan models.UserCredentials, failure chan error) {
|
func askToPasteJwtToken(success chan models.UserCredentials, failure chan error) {
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 5)
|
||||||
fmt.Println("\n\nOnce login is completed via browser, the CLI should be authenticated automatically.")
|
fmt.Println("\n\nOnce login is completed via browser, the CLI should be authenticated automatically.")
|
||||||
fmt.Println("However, if browser fails to communicate with the CLI, please paste the token from the browser below.")
|
fmt.Println("However, if browser fails to communicate with the CLI, please paste the token from the browser below.")
|
||||||
@@ -807,26 +807,22 @@ func browserCliLogin() (models.UserCredentials, error) {
|
|||||||
|
|
||||||
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
|
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
|
||||||
|
|
||||||
stdin := readline.NewCancelableStdin(os.Stdin)
|
|
||||||
go http.Serve(listener, corsHandler)
|
go http.Serve(listener, corsHandler)
|
||||||
go askToPasteJwtToken(stdin, success, failure)
|
go askToPasteJwtToken(success, failure)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case loginResponse := <-success:
|
case loginResponse := <-success:
|
||||||
_ = closeListener(&listener)
|
_ = closeListener(&listener)
|
||||||
_ = stdin.Close()
|
|
||||||
fmt.Println("Browser login successful")
|
fmt.Println("Browser login successful")
|
||||||
return loginResponse, nil
|
return loginResponse, nil
|
||||||
|
|
||||||
case err := <-failure:
|
case err := <-failure:
|
||||||
serverErr := closeListener(&listener)
|
serverErr := closeListener(&listener)
|
||||||
stdErr := stdin.Close()
|
return models.UserCredentials{}, errors.Join(err, serverErr)
|
||||||
return models.UserCredentials{}, errors.Join(err, serverErr, stdErr)
|
|
||||||
|
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
_ = closeListener(&listener)
|
_ = closeListener(&listener)
|
||||||
_ = stdin.Close()
|
|
||||||
return models.UserCredentials{}, errors.New("server timeout")
|
return models.UserCredentials{}, errors.New("server timeout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v2/workspace/{slug}/cas"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v2/workspace/{slug}/certificates"
|
||||||
|
---
|
||||||
@@ -654,6 +654,7 @@
|
|||||||
{
|
{
|
||||||
"group": "Certificate Authorities",
|
"group": "Certificate Authorities",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
"api-reference/endpoints/certificate-authorities/list",
|
||||||
"api-reference/endpoints/certificate-authorities/create",
|
"api-reference/endpoints/certificate-authorities/create",
|
||||||
"api-reference/endpoints/certificate-authorities/read",
|
"api-reference/endpoints/certificate-authorities/read",
|
||||||
"api-reference/endpoints/certificate-authorities/update",
|
"api-reference/endpoints/certificate-authorities/update",
|
||||||
@@ -669,6 +670,7 @@
|
|||||||
{
|
{
|
||||||
"group": "Certificates",
|
"group": "Certificates",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
"api-reference/endpoints/certificates/list",
|
||||||
"api-reference/endpoints/certificates/read",
|
"api-reference/endpoints/certificates/read",
|
||||||
"api-reference/endpoints/certificates/revoke",
|
"api-reference/endpoints/certificates/revoke",
|
||||||
"api-reference/endpoints/certificates/delete",
|
"api-reference/endpoints/certificates/delete",
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ Used to configure platform-specific security and operational settings
|
|||||||
https://app.infisical.com).
|
https://app.infisical.com).
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField query="PORT" type="int" default="8080" optional>
|
||||||
|
Specifies the internal port on which the application listens.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional>
|
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional>
|
||||||
Telemetry helps us improve Infisical but if you want to dsiable it you may set this to `false`.
|
Telemetry helps us improve Infisical but if you want to dsiable it you may set this to `false`.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const useUpdateOrgRole = () => {
|
|||||||
data: { role }
|
data: { role }
|
||||||
} = await apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, {
|
} = await apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, {
|
||||||
...dto,
|
...dto,
|
||||||
permissions: permissions?.length ? packRules(permissions) : []
|
permissions: permissions?.length ? packRules(permissions) : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
return role;
|
return role;
|
||||||
|
|||||||
@@ -168,16 +168,27 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
const { token: newJwtToken } = await selectOrganization({ organizationId });
|
const { token: newJwtToken } = await selectOrganization({ organizationId });
|
||||||
|
|
||||||
const instance = axios.create();
|
const instance = axios.create();
|
||||||
await instance.post(cliUrl, {
|
const payload = {
|
||||||
...isCliLoginSuccessful.loginResponse,
|
...isCliLoginSuccessful.loginResponse,
|
||||||
JTWToken: newJwtToken
|
JTWToken: newJwtToken
|
||||||
|
};
|
||||||
|
await instance.post(cliUrl, payload).catch(() => {
|
||||||
|
// if error happens to communicate we set the token with an expiry in sessino storage
|
||||||
|
// the cli-redirect page has logic to show this to user and ask them to paste it in terminal
|
||||||
|
sessionStorage.setItem(
|
||||||
|
SessionStorageKeys.CLI_TERMINAL_TOKEN,
|
||||||
|
JSON.stringify({
|
||||||
|
expiry: formatISO(addSeconds(new Date(), 30)),
|
||||||
|
data: window.btoa(JSON.stringify(payload))
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
router.push("/cli-redirect");
|
||||||
await navigateUserToOrg(router, organizationId);
|
return;
|
||||||
}
|
}
|
||||||
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs
|
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs
|
||||||
// if the user has no orgs, navigate to the create org page
|
// if the user has no orgs, navigate to the create org page
|
||||||
else {
|
|
||||||
const userOrgs = await fetchOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
// case: user has orgs, so we navigate the user to select an org
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
@@ -189,7 +200,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
else {
|
else {
|
||||||
await navigateUserToOrg(router);
|
await navigateUserToOrg(router);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const isLoginSuccessful = await attemptLoginMfa({
|
const isLoginSuccessful = await attemptLoginMfa({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { addSeconds, formatISO } from "date-fns";
|
||||||
import jwt_decode from "jwt-decode";
|
import jwt_decode from "jwt-decode";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -12,6 +13,7 @@ import attemptLogin from "@app/components/utilities/attemptLogin";
|
|||||||
import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config";
|
import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config";
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { Button, Input, Spinner } from "@app/components/v2";
|
import { Button, Input, Spinner } from "@app/components/v2";
|
||||||
|
import { SessionStorageKeys } from "@app/const";
|
||||||
import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api";
|
import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api";
|
||||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
|
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
|
||||||
@@ -79,11 +81,24 @@ export const PasswordStep = ({
|
|||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
console.log("organization id was present. new JWT token to be used in CLI:", newJwtToken);
|
console.log("organization id was present. new JWT token to be used in CLI:", newJwtToken);
|
||||||
const instance = axios.create();
|
const instance = axios.create();
|
||||||
await instance.post(cliUrl, {
|
const payload = {
|
||||||
privateKey,
|
privateKey,
|
||||||
email,
|
email,
|
||||||
JTWToken: newJwtToken
|
JTWToken: newJwtToken
|
||||||
|
};
|
||||||
|
await instance.post(cliUrl, payload).catch(() => {
|
||||||
|
// if error happens to communicate we set the token with an expiry in sessino storage
|
||||||
|
// the cli-redirect page has logic to show this to user and ask them to paste it in terminal
|
||||||
|
sessionStorage.setItem(
|
||||||
|
SessionStorageKeys.CLI_TERMINAL_TOKEN,
|
||||||
|
JSON.stringify({
|
||||||
|
expiry: formatISO(addSeconds(new Date(), 30)),
|
||||||
|
data: window.btoa(JSON.stringify(payload))
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
router.push("/cli-redirect");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await navigateUserToOrg(router, organizationId);
|
await navigateUserToOrg(router, organizationId);
|
||||||
@@ -165,26 +180,35 @@ export const PasswordStep = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const instance = axios.create();
|
const instance = axios.create();
|
||||||
await instance.post(cliUrl, {
|
const payload = {
|
||||||
...isCliLoginSuccessful.loginResponse,
|
...isCliLoginSuccessful.loginResponse,
|
||||||
JTWToken: newJwtToken
|
JTWToken: newJwtToken
|
||||||
|
};
|
||||||
|
await instance.post(cliUrl, payload).catch(() => {
|
||||||
|
// if error happens to communicate we set the token with an expiry in sessino storage
|
||||||
|
// the cli-redirect page has logic to show this to user and ask them to paste it in terminal
|
||||||
|
sessionStorage.setItem(
|
||||||
|
SessionStorageKeys.CLI_TERMINAL_TOKEN,
|
||||||
|
JSON.stringify({
|
||||||
|
expiry: formatISO(addSeconds(new Date(), 30)),
|
||||||
|
data: window.btoa(JSON.stringify(payload))
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
router.push("/cli-redirect");
|
||||||
await navigateUserToOrg(router, organizationId);
|
return;
|
||||||
}
|
}
|
||||||
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs
|
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs
|
||||||
// if the user has no orgs, navigate to the create org page
|
// if the user has no orgs, navigate to the create org page
|
||||||
else {
|
const userOrgs = await fetchOrganizations();
|
||||||
const userOrgs = await fetchOrganizations();
|
|
||||||
|
|
||||||
// case: user has orgs, so we navigate the user to select an org
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateToSelectOrganization(callbackPort);
|
navigateToSelectOrganization(callbackPort);
|
||||||
}
|
}
|
||||||
// case: no orgs found, so we navigate the user to create an org
|
// case: no orgs found, so we navigate the user to create an org
|
||||||
else {
|
else {
|
||||||
await navigateUserToOrg(router);
|
await navigateUserToOrg(router);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
-249
@@ -1,249 +0,0 @@
|
|||||||
import { useForm } from "react-hook-form";
|
|
||||||
import {
|
|
||||||
faArrowLeft,
|
|
||||||
faCog,
|
|
||||||
faContactCard,
|
|
||||||
faMagnifyingGlass,
|
|
||||||
faMoneyBill,
|
|
||||||
faServer,
|
|
||||||
faSignIn,
|
|
||||||
faUser,
|
|
||||||
faUserCog,
|
|
||||||
faUsers
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
|
||||||
import { useOrganization } from "@app/context";
|
|
||||||
import { useCreateOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
|
||||||
import { TOrgRole } from "@app/hooks/api/roles/types";
|
|
||||||
|
|
||||||
import {
|
|
||||||
formRolePermission2API,
|
|
||||||
formSchema,
|
|
||||||
rolePermission2Form,
|
|
||||||
TFormSchema
|
|
||||||
} from "./OrgRoleModifySection.utils";
|
|
||||||
import { SimpleLevelPermissionOption } from "./SimpleLevelPermissionOptions";
|
|
||||||
import { WorkspacePermission } from "./WorkspacePermission";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
role?: TOrgRole;
|
|
||||||
onGoBack: VoidFunction;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SIMPLE_PERMISSION_OPTIONS = [
|
|
||||||
{
|
|
||||||
title: "User management",
|
|
||||||
subtitle: "Invite, view and remove users from the organization",
|
|
||||||
icon: faUser,
|
|
||||||
formName: "member"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Group management",
|
|
||||||
subtitle: "Invite, view and remove user groups from the organization",
|
|
||||||
icon: faUsers,
|
|
||||||
formName: "groups"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Machine identity management",
|
|
||||||
subtitle: "Create, view, update and remove (machine) identities from the organization",
|
|
||||||
icon: faServer,
|
|
||||||
formName: "identity"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Billing & usage",
|
|
||||||
subtitle: "Modify organization subscription plan",
|
|
||||||
icon: faMoneyBill,
|
|
||||||
formName: "billing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Role management",
|
|
||||||
subtitle: "Create, modify and remove organization roles",
|
|
||||||
icon: faUserCog,
|
|
||||||
formName: "role"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Incident Contacts",
|
|
||||||
subtitle: "Incident contacts management control",
|
|
||||||
icon: faContactCard,
|
|
||||||
formName: "incident-contact"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Organization profile",
|
|
||||||
subtitle: "View & update organization metadata such as name",
|
|
||||||
icon: faCog,
|
|
||||||
formName: "settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Secret Scanning",
|
|
||||||
subtitle: "Secret scanning management control",
|
|
||||||
icon: faMagnifyingGlass,
|
|
||||||
formName: "secret-scanning"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "SSO",
|
|
||||||
subtitle: "Define organization level SSO requirements",
|
|
||||||
icon: faSignIn,
|
|
||||||
formName: "sso"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "LDAP",
|
|
||||||
subtitle: "Define organization level LDAP requirements",
|
|
||||||
icon: faSignIn,
|
|
||||||
formName: "ldap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "SCIM",
|
|
||||||
subtitle: "Define organization level SCIM requirements",
|
|
||||||
icon: faUsers,
|
|
||||||
formName: "scim"
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
|
||||||
const isNonEditable = ["owner", "admin", "member", "no-access"].includes(role?.slug || "");
|
|
||||||
const isNewRole = !role?.slug;
|
|
||||||
const { currentOrg } = useOrganization();
|
|
||||||
const orgId = currentOrg?.id || "";
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
register,
|
|
||||||
formState: { isSubmitting, isDirty, errors },
|
|
||||||
setValue,
|
|
||||||
control
|
|
||||||
} = useForm<TFormSchema>({
|
|
||||||
defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {},
|
|
||||||
resolver: zodResolver(formSchema)
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutateAsync: createRole } = useCreateOrgRole();
|
|
||||||
const { mutateAsync: updateRole } = useUpdateOrgRole();
|
|
||||||
|
|
||||||
const handleRoleUpdate = async (el: TFormSchema) => {
|
|
||||||
if (!role?.id) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await updateRole({
|
|
||||||
orgId,
|
|
||||||
id: role?.id,
|
|
||||||
...el,
|
|
||||||
permissions: formRolePermission2API(el.permissions)
|
|
||||||
});
|
|
||||||
createNotification({ type: "success", text: "Successfully updated role" });
|
|
||||||
onGoBack();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
createNotification({ type: "error", text: "Failed to update role" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormSubmit = async (el: TFormSchema) => {
|
|
||||||
if (!isNewRole) {
|
|
||||||
await handleRoleUpdate(el);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await createRole({
|
|
||||||
orgId,
|
|
||||||
...el,
|
|
||||||
permissions: formRolePermission2API(el.permissions)
|
|
||||||
});
|
|
||||||
createNotification({ type: "success", text: "Created new role" });
|
|
||||||
onGoBack();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
createNotification({ type: "error", text: "Failed to create role" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
|
||||||
<div className="mb-2 flex items-center justify-between">
|
|
||||||
<h1 className="text-xl font-semibold text-mineshaft-100">
|
|
||||||
{isNewRole ? "New" : "Edit"} Role
|
|
||||||
</h1>
|
|
||||||
<Button
|
|
||||||
onClick={onGoBack}
|
|
||||||
variant="outline_bg"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faArrowLeft} />}
|
|
||||||
>
|
|
||||||
Go back
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="mb-8 text-gray-400">
|
|
||||||
Organization-level roles allow you to define permissions for resources at a high level
|
|
||||||
across the organization
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col space-y-6">
|
|
||||||
<FormControl
|
|
||||||
label="Name"
|
|
||||||
isRequired
|
|
||||||
className="mb-0"
|
|
||||||
isError={Boolean(errors?.name)}
|
|
||||||
errorText={errors?.name?.message}
|
|
||||||
>
|
|
||||||
<Input {...register("name")} placeholder="Billing Team" isReadOnly={isNonEditable} />
|
|
||||||
</FormControl>
|
|
||||||
<FormControl
|
|
||||||
label="Slug"
|
|
||||||
isRequired
|
|
||||||
isError={Boolean(errors?.slug)}
|
|
||||||
errorText={errors?.slug?.message}
|
|
||||||
>
|
|
||||||
<Input {...register("slug")} placeholder="biller" isReadOnly={isNonEditable} />
|
|
||||||
</FormControl>
|
|
||||||
<FormControl
|
|
||||||
label="Description"
|
|
||||||
helperText="A short description about this role"
|
|
||||||
isError={Boolean(errors?.description)}
|
|
||||||
errorText={errors?.description?.message}
|
|
||||||
>
|
|
||||||
<Input {...register("description")} isReadOnly={isNonEditable} />
|
|
||||||
</FormControl>
|
|
||||||
<div className="flex items-center justify-between border-t border-t-mineshaft-800 pt-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-medium">Add Permission</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="">
|
|
||||||
<WorkspacePermission
|
|
||||||
isNonEditable={isNonEditable}
|
|
||||||
control={control}
|
|
||||||
setValue={setValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{SIMPLE_PERMISSION_OPTIONS.map(({ title, subtitle, icon, formName }) => (
|
|
||||||
<div key={`permission-${title}`}>
|
|
||||||
<SimpleLevelPermissionOption
|
|
||||||
isNonEditable={isNonEditable}
|
|
||||||
control={control}
|
|
||||||
setValue={setValue}
|
|
||||||
icon={icon}
|
|
||||||
title={title}
|
|
||||||
subtitle={subtitle}
|
|
||||||
formName={formName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-12 flex items-center space-x-4">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
isDisabled={isSubmitting || isNonEditable || !isDirty}
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
>
|
|
||||||
{isNewRole ? "Create Role" : "Save Role"}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onGoBack} variant="outline_bg">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
-204
@@ -1,204 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
formName: keyof Omit<Exclude<TFormSchema["permissions"], undefined>, "workspace">;
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
icon: IconProp;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "View" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Modify" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const SECRET_SCANNING_PERMISSIONS = [
|
|
||||||
{ action: "read", label: "View risks" },
|
|
||||||
{ action: "create", label: "Add integrations" },
|
|
||||||
{ action: "edit", label: "Edit risk status" },
|
|
||||||
{ action: "delete", label: "Remove integrations" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const INCIDENT_CONTACTS_PERMISSIONS = [
|
|
||||||
{ action: "read", label: "View contacts" },
|
|
||||||
{ action: "create", label: "Add new contacts" },
|
|
||||||
{ action: "edit", label: "Edit contacts" },
|
|
||||||
{ action: "delete", label: "Remove contacts" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const MEMBERS_PERMISSIONS = [
|
|
||||||
{ action: "read", label: "View all members" },
|
|
||||||
{ action: "create", label: "Invite members" },
|
|
||||||
{ action: "edit", label: "Edit members" },
|
|
||||||
{ action: "delete", label: "Remove members" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const BILLING_PERMISSIONS = [
|
|
||||||
{ action: "read", label: "View bills" },
|
|
||||||
{ action: "create", label: "Add payment methods" },
|
|
||||||
{ action: "edit", label: "Edit payments" },
|
|
||||||
{ action: "delete", label: "Remove payments" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const getPermissionList = (option: Props["formName"]) => {
|
|
||||||
switch (option) {
|
|
||||||
case "secret-scanning":
|
|
||||||
return SECRET_SCANNING_PERMISSIONS;
|
|
||||||
case "billing":
|
|
||||||
return BILLING_PERMISSIONS;
|
|
||||||
case "incident-contact":
|
|
||||||
return INCIDENT_CONTACTS_PERMISSIONS;
|
|
||||||
case "member":
|
|
||||||
return MEMBERS_PERMISSIONS;
|
|
||||||
default:
|
|
||||||
return PERMISSIONS;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SimpleLevelPermissionOption = ({
|
|
||||||
isNonEditable,
|
|
||||||
setValue,
|
|
||||||
control,
|
|
||||||
formName,
|
|
||||||
subtitle,
|
|
||||||
title,
|
|
||||||
icon
|
|
||||||
}: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: `permissions.${formName}`
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
const totalActions = PERMISSIONS.length;
|
|
||||||
const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
|
||||||
if (score === 0) return Permission.NoAccess;
|
|
||||||
if (score === totalActions) return Permission.FullAccess;
|
|
||||||
if (score === 1 && rule?.read) return Permission.ReadOnly;
|
|
||||||
|
|
||||||
return Permission.Custom;
|
|
||||||
}, [rule, isCustom]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
}, [selectedPermissionCategory]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
`permissions.${formName}`,
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
`permissions.${formName}`,
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
`permissions.${formName}`,
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
`permissions.${formName}`,
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={icon} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-grow flex-col">
|
|
||||||
<div className="mb-1 text-lg font-medium">{title}</div>
|
|
||||||
<div className="text-xs font-light">{subtitle}</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
getPermissionList(formName).map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.${formName}.${action}`}
|
|
||||||
key={`permissions.${formName}.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.${formName}.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+1
-1
@@ -8,7 +8,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
import { TFormSchema } from "../../../../RolePage/components/OrgRoleModifySection.utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isNonEditable?: boolean;
|
isNonEditable?: boolean;
|
||||||
|
|||||||
-1
@@ -1 +0,0 @@
|
|||||||
export { OrgRoleModifySection } from "./OrgRoleModifySection";
|
|
||||||
+1
-19
@@ -1,27 +1,9 @@
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
import { usePopUp } from "@app/hooks";
|
|
||||||
import { TOrgRole } from "@app/hooks/api/roles/types";
|
|
||||||
|
|
||||||
import { OrgRoleModifySection } from "./OrgRoleModifySection";
|
|
||||||
import { OrgRoleTable } from "./OrgRoleTable";
|
import { OrgRoleTable } from "./OrgRoleTable";
|
||||||
|
|
||||||
export const OrgRoleTabSection = () => {
|
export const OrgRoleTabSection = () => {
|
||||||
const { popUp, handlePopUpClose } = usePopUp(["editRole"] as const);
|
return (
|
||||||
return popUp.editRole.isOpen ? (
|
|
||||||
<motion.div
|
|
||||||
key="role-modify"
|
|
||||||
transition={{ duration: 0.1 }}
|
|
||||||
initial={{ opacity: 0, translateX: 30 }}
|
|
||||||
animate={{ opacity: 1, translateX: 0 }}
|
|
||||||
exit={{ opacity: 0, translateX: 30 }}
|
|
||||||
>
|
|
||||||
<OrgRoleModifySection
|
|
||||||
role={popUp.editRole.data as TOrgRole}
|
|
||||||
onGoBack={() => handlePopUpClose("editRole")}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
) : (
|
|
||||||
<motion.div
|
<motion.div
|
||||||
key="role-list"
|
key="role-list"
|
||||||
transition={{ duration: 0.1 }}
|
transition={{ duration: 0.1 }}
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { TFormSchema } from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils";
|
import { TFormSchema } from "@app/views/Org/RolePage/components/OrgRoleModifySection.utils";
|
||||||
|
|
||||||
const PERMISSIONS = [
|
const PERMISSIONS = [
|
||||||
{ action: "read", label: "View" },
|
{ action: "read", label: "View" },
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button , Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2";
|
import { Button, Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { useOrganization } from "@app/context";
|
||||||
import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
formSchema,
|
formSchema,
|
||||||
rolePermission2Form,
|
rolePermission2Form,
|
||||||
TFormSchema
|
TFormSchema
|
||||||
} from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils";
|
} from "@app/views/Org/RolePage/components/OrgRoleModifySection.utils";
|
||||||
|
|
||||||
import { RolePermissionRow } from "./RolePermissionRow";
|
import { RolePermissionRow } from "./RolePermissionRow";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user