mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 23:26:20 +00:00
Merge pull request #1560 from Infisical/toggle-invite-org
Update UI for Org Settings Page
This commit is contained in:
@@ -164,15 +164,17 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z
|
||||||
organizationId: z.string().trim(),
|
.object({
|
||||||
isActive: z.boolean().optional(),
|
isActive: z.boolean(),
|
||||||
url: z.string().trim().optional(),
|
url: z.string().trim(),
|
||||||
bindDN: z.string().trim().optional(),
|
bindDN: z.string().trim(),
|
||||||
bindPass: z.string().trim().optional(),
|
bindPass: z.string().trim(),
|
||||||
searchBase: z.string().trim().optional(),
|
searchBase: z.string().trim(),
|
||||||
caCert: z.string().trim().optional()
|
caCert: z.string().trim()
|
||||||
}),
|
})
|
||||||
|
.partial()
|
||||||
|
.merge(z.object({ organizationId: z.string() })),
|
||||||
response: {
|
response: {
|
||||||
200: LdapConfigsSchema
|
200: LdapConfigsSchema
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,21 +180,21 @@ export const ldapConfigServiceFactory = ({
|
|||||||
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
if (bindDN) {
|
if (bindDN !== undefined) {
|
||||||
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
|
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
|
||||||
updateQuery.encryptedBindDN = encryptedBindDN;
|
updateQuery.encryptedBindDN = encryptedBindDN;
|
||||||
updateQuery.bindDNIV = bindDNIV;
|
updateQuery.bindDNIV = bindDNIV;
|
||||||
updateQuery.bindDNTag = bindDNTag;
|
updateQuery.bindDNTag = bindDNTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindPass) {
|
if (bindPass !== undefined) {
|
||||||
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
|
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
|
||||||
updateQuery.encryptedBindPass = encryptedBindPass;
|
updateQuery.encryptedBindPass = encryptedBindPass;
|
||||||
updateQuery.bindPassIV = bindPassIV;
|
updateQuery.bindPassIV = bindPassIV;
|
||||||
updateQuery.bindPassTag = bindPassTag;
|
updateQuery.bindPassTag = bindPassTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caCert) {
|
if (caCert !== undefined) {
|
||||||
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
|
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
|
||||||
updateQuery.encryptedCACert = encryptedCACert;
|
updateQuery.encryptedCACert = encryptedCACert;
|
||||||
updateQuery.caCertIV = caCertIV;
|
updateQuery.caCertIV = caCertIV;
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
if (entryPoint) {
|
if (entryPoint !== undefined) {
|
||||||
const {
|
const {
|
||||||
ciphertext: encryptedEntryPoint,
|
ciphertext: encryptedEntryPoint,
|
||||||
iv: entryPointIV,
|
iv: entryPointIV,
|
||||||
@@ -182,18 +182,19 @@ export const samlConfigServiceFactory = ({
|
|||||||
updateQuery.entryPointIV = entryPointIV;
|
updateQuery.entryPointIV = entryPointIV;
|
||||||
updateQuery.entryPointTag = entryPointTag;
|
updateQuery.entryPointTag = entryPointTag;
|
||||||
}
|
}
|
||||||
if (issuer) {
|
if (issuer !== undefined) {
|
||||||
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
||||||
updateQuery.encryptedIssuer = encryptedIssuer;
|
updateQuery.encryptedIssuer = encryptedIssuer;
|
||||||
updateQuery.issuerIV = issuerIV;
|
updateQuery.issuerIV = issuerIV;
|
||||||
updateQuery.issuerTag = issuerTag;
|
updateQuery.issuerTag = issuerTag;
|
||||||
}
|
}
|
||||||
if (cert) {
|
if (cert !== undefined) {
|
||||||
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
||||||
updateQuery.encryptedCert = encryptedCert;
|
updateQuery.encryptedCert = encryptedCert;
|
||||||
updateQuery.certIV = certIV;
|
updateQuery.certIV = certIV;
|
||||||
updateQuery.certTag = certTag;
|
updateQuery.certTag = certTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
|
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
|
||||||
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
|
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
|
||||||
|
|
||||||
|
|||||||
@@ -159,14 +159,6 @@
|
|||||||
"documentation/platform/ldap/general"
|
"documentation/platform/ldap/general"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"group": "LDAP",
|
|
||||||
"pages": [
|
|
||||||
"documentation/platform/ldap/overview",
|
|
||||||
"documentation/platform/ldap/jumpcloud",
|
|
||||||
"documentation/platform/ldap/general"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"group": "SCIM",
|
"group": "SCIM",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const OrgSettingsPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center bg-bunker-800 text-white w-full py-6">
|
<div className="flex justify-center bg-bunker-800 text-white w-full py-6">
|
||||||
<div className="max-w-7xl w-full px-6">
|
<div className="max-w-4xl w-full px-6">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import {
|
|||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const LDAPFormSchema = z.object({
|
const LDAPFormSchema = z.object({
|
||||||
url: z.string().min(1, "URL is requiredx"),
|
url: z.string().default(""),
|
||||||
bindDN: z.string().min(1, "Bind DN is requiredx"),
|
bindDN: z.string().default(""),
|
||||||
bindPass: z.string().min(1, "Bind Pass is required"),
|
bindPass: z.string().default(""),
|
||||||
searchBase: z.string().min(1, "Search Base is required"),
|
searchBase: z.string().default(""),
|
||||||
caCert: z.string().optional()
|
caCert: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ export const LDAPModal = ({
|
|||||||
reset();
|
reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title="Add LDAP">
|
<ModalContent title="Manage LDAP configuration">
|
||||||
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { OrgSSOSection } from "./OrgSSOSection";
|
|||||||
export const OrgAuthTab = withPermission(
|
export const OrgAuthTab = withPermission(
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
<OrgGeneralAuthSection />
|
<OrgGeneralAuthSection />
|
||||||
<OrgSSOSection />
|
<OrgSSOSection />
|
||||||
<OrgLDAPSection />
|
<OrgLDAPSection />
|
||||||
|
|||||||
+34
-15
@@ -59,25 +59,44 @@ export const OrgGeneralAuthSection = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<>
|
||||||
<h2 className="flex-1 text-xl font-semibold text-white mb-8">Settings</h2>
|
{/* <div className="py-4">
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
<div className="mb-2 flex justify-between">
|
||||||
{(isAllowed) => (
|
<h3 className="text-md text-mineshaft-100">Allow users to send invites</h3>
|
||||||
<Switch
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||||
id="enforce-org-auth"
|
{(isAllowed) => (
|
||||||
onCheckedChange={(value) => handleEnforceOrgAuthToggle(value)}
|
<Switch
|
||||||
isChecked={currentOrg?.authEnforced ?? false}
|
id="allow-org-invites"
|
||||||
isDisabled={!isAllowed}
|
onCheckedChange={(value) => handleEnforceOrgAuthToggle(value)}
|
||||||
>
|
isChecked={currentOrg?.authEnforced ?? false}
|
||||||
Enforce SAML SSO
|
isDisabled={!isAllowed}
|
||||||
</Switch>
|
/>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Allow members to invite new users to this organization</p>
|
||||||
|
</div> */}
|
||||||
|
<div className="py-4">
|
||||||
|
<div className="mb-2 flex justify-between">
|
||||||
|
<h3 className="text-md text-mineshaft-100">Enforce SAML SSO</h3>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Switch
|
||||||
|
id="enforce-org-auth"
|
||||||
|
onCheckedChange={(value) => handleEnforceOrgAuthToggle(value)}
|
||||||
|
isChecked={currentOrg?.authEnforced ?? false}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Enforce members to authenticate via SAML to access this organization</p>
|
||||||
|
</div>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can enforce SAML SSO if you switch to Infisical's Pro plan."
|
text="You can enforce SAML SSO if you switch to Infisical's Pro plan."
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+37
-35
@@ -1,6 +1,3 @@
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +24,7 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const { data, isLoading } = useGetLDAPConfig(currentOrg?.id ?? "");
|
const { data } = useGetLDAPConfig(currentOrg?.id ?? "");
|
||||||
const { mutateAsync } = useUpdateLDAPConfig();
|
const { mutateAsync } = useUpdateLDAPConfig();
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"addLDAP",
|
"addLDAP",
|
||||||
@@ -88,50 +85,55 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
<>
|
||||||
<div className="flex items-center mb-8">
|
<hr className="border-mineshaft-600" />
|
||||||
<h2 className="text-xl font-semibold flex-1 text-white">LDAP</h2>
|
<div className="py-4">
|
||||||
{!isLoading && (
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
<h2 className="text-md text-mineshaft-100">LDAP</h2>
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Ldap}>
|
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Ldap}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
onClick={addLDAPBtnClick}
|
onClick={addLDAPBtnClick}
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
>
|
>
|
||||||
Configure
|
Manage
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
)}
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Manage LDAP authentication configuration</p>
|
||||||
</div>
|
</div>
|
||||||
{data && (
|
{data && (
|
||||||
<div className="mb-4">
|
<div className="py-4">
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Ldap}>
|
<div className="mb-2 flex items-center justify-between">
|
||||||
{(isAllowed) => (
|
<h2 className="text-md text-mineshaft-100">Enable LDAP</h2>
|
||||||
<Switch
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Ldap}>
|
||||||
id="enable-saml-sso"
|
{(isAllowed) => (
|
||||||
onCheckedChange={(value) => handleLDAPToggle(value)}
|
<Switch
|
||||||
isChecked={data ? data.isActive : false}
|
id="enable-saml-sso"
|
||||||
isDisabled={!isAllowed}
|
onCheckedChange={(value) => handleLDAPToggle(value)}
|
||||||
>
|
isChecked={data ? data.isActive : false}
|
||||||
Enable
|
isDisabled={!isAllowed}
|
||||||
</Switch>
|
>
|
||||||
)}
|
Enable
|
||||||
</OrgPermissionCan>
|
</Switch>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Allow members to authenticate into Infisical with LDAP</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<LDAPModal
|
<LDAPModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpClose={handlePopUpClose}
|
handlePopUpClose={handlePopUpClose}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can use LDAP authentication if you switch to Infisical's Enterprise plan."
|
text="You can use LDAP authentication if you switch to Infisical's Enterprise plan."
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+32
-28
@@ -1,6 +1,3 @@
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -64,40 +61,47 @@ export const OrgScimSection = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<>
|
||||||
<div className="mb-8 flex items-center">
|
<hr className="border-mineshaft-600" />
|
||||||
<h2 className="flex-1 text-xl font-semibold text-white">SCIM</h2>
|
<div className="py-4">
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Scim}>
|
<div className="mb-2 flex justify-between items-center">
|
||||||
|
<h2 className="text-md text-mineshaft-100">SCIM</h2>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Scim}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
onClick={addScimTokenBtnClick}
|
onClick={addScimTokenBtnClick}
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
>
|
>
|
||||||
Configure
|
Manage
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Manage SCIM configuration</p>
|
||||||
|
</div>
|
||||||
|
<div className="py-4">
|
||||||
|
<div className="mb-2 flex justify-between items-center">
|
||||||
|
<h2 className="text-md text-mineshaft-100">Enable SCIM</h2>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Scim}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Switch
|
||||||
|
id="enable-scim"
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
if (subscription?.scim) {
|
||||||
|
handleEnableSCIMToggle(value)
|
||||||
|
} else {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isChecked={currentOrg?.scimEnabled ?? false}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Allow member provisioning/deprovisioning with SCIM</p>
|
||||||
</div>
|
</div>
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Scim}>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Switch
|
|
||||||
id="enable-scim"
|
|
||||||
onCheckedChange={(value) => {
|
|
||||||
if (subscription?.scim) {
|
|
||||||
handleEnableSCIMToggle(value)
|
|
||||||
} else {
|
|
||||||
handlePopUpOpen("upgradePlan");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
isChecked={currentOrg?.scimEnabled ?? false}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
>
|
|
||||||
Enable
|
|
||||||
</Switch>
|
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
|
||||||
<ScimTokenModal
|
<ScimTokenModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
@@ -108,6 +112,6 @@ export const OrgScimSection = () => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+49
-46
@@ -1,6 +1,3 @@
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
||||||
@@ -81,49 +78,55 @@ export const OrgSSOSection = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<>
|
||||||
<div className="mb-8 flex items-center">
|
<hr className="border-mineshaft-600" />
|
||||||
<h2 className="flex-1 text-xl font-semibold text-white">SAML</h2>
|
<div className="py-4">
|
||||||
{!isLoading && (
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Sso}>
|
<h2 className="text-md text-mineshaft-100">SAML</h2>
|
||||||
{(isAllowed) => (
|
{!isLoading && (
|
||||||
<Button
|
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Sso}>
|
||||||
onClick={addSSOBtnClick}
|
{(isAllowed) => (
|
||||||
colorSchema="secondary"
|
<Button
|
||||||
isDisabled={!isAllowed}
|
onClick={addSSOBtnClick}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
colorSchema="secondary"
|
||||||
>
|
isDisabled={!isAllowed}
|
||||||
Configure
|
>
|
||||||
</Button>
|
Manage
|
||||||
)}
|
</Button>
|
||||||
</OrgPermissionCan>
|
)}
|
||||||
)}
|
</OrgPermissionCan>
|
||||||
</div>
|
)}
|
||||||
{/* {data && ( */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Switch
|
|
||||||
id="enable-saml-sso"
|
|
||||||
onCheckedChange={(value) => handleSamlSSOToggle(value)}
|
|
||||||
isChecked={data ? data.isActive : false}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
>
|
|
||||||
Enable
|
|
||||||
</Switch>
|
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
|
||||||
</div>
|
</div>
|
||||||
<SSOModal
|
<p className="text-sm text-mineshaft-300">Manage SAML authentication configuration</p>
|
||||||
popUp={popUp}
|
</div>
|
||||||
handlePopUpClose={handlePopUpClose}
|
<div className="py-4">
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
<div className="mb-2 flex items-center justify-between">
|
||||||
/>
|
<h2 className="text-md text-mineshaft-100">Enable SAML</h2>
|
||||||
<UpgradePlanModal
|
{!isLoading && (
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
{(isAllowed) => (
|
||||||
text="You can use SAML SSO if you switch to Infisical's Pro plan."
|
<Switch
|
||||||
/>
|
id="enable-saml-sso"
|
||||||
</div>
|
onCheckedChange={(value) => handleSamlSSOToggle(value)}
|
||||||
|
isChecked={data ? data.isActive : false}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">Allow members to authenticate into Infisical with SAML</p>
|
||||||
|
</div>
|
||||||
|
<SSOModal
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpClose={handlePopUpClose}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text="You can use SAML SSO if you switch to Infisical's Pro plan."
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as yup from "yup";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import {
|
||||||
@@ -32,16 +32,16 @@ const ssoAuthProviders = [
|
|||||||
{ label: "Google SAML", value: AuthProvider.GOOGLE_SAML }
|
{ label: "Google SAML", value: AuthProvider.GOOGLE_SAML }
|
||||||
];
|
];
|
||||||
|
|
||||||
const schema = yup
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
authProvider: yup.string().required("SSO Type is required"),
|
authProvider: z.string().min(1, "SSO Type is required"),
|
||||||
entryPoint: yup.string().required("IdP entrypoint is required"),
|
entryPoint: z.string().default(""),
|
||||||
issuer: yup.string().required("Issuer string is required"),
|
issuer: z.string().default(""),
|
||||||
cert: yup.string().required("IdP's public signing certificate is required")
|
cert: z.string().default("")
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export type AddSSOFormData = yup.InferType<typeof schema>;
|
export type AddSSOFormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["addSSO"]>;
|
popUp: UsePopUpState<["addSSO"]>;
|
||||||
@@ -60,7 +60,7 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
authProvider: AuthProvider.OKTA_SAML
|
authProvider: AuthProvider.OKTA_SAML
|
||||||
},
|
},
|
||||||
resolver: yupResolver(schema)
|
resolver: zodResolver(schema)
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -173,7 +173,7 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
reset();
|
reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title="Add SSO">
|
<ModalContent title="Manage SAML configuration">
|
||||||
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+16
-13
@@ -45,18 +45,21 @@ export const OrgDeleteSection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<>
|
||||||
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
|
<hr className="border-mineshaft-600" />
|
||||||
<Button
|
<div className="py-4">
|
||||||
isLoading={isLoading}
|
<p className="mb-4 text-md text-mineshaft-100">Danger Zone</p>
|
||||||
colorSchema="danger"
|
<Button
|
||||||
variant="outline_bg"
|
isLoading={isLoading}
|
||||||
type="submit"
|
colorSchema="danger"
|
||||||
onClick={() => handlePopUpOpen("deleteOrg")}
|
variant="outline_bg"
|
||||||
isDisabled={Boolean(membership && membership.role !== "admin")}
|
type="submit"
|
||||||
>
|
onClick={() => handlePopUpOpen("deleteOrg")}
|
||||||
{`Delete ${currentOrg?.name}`}
|
isDisabled={Boolean(membership && membership.role !== "admin")}
|
||||||
</Button>
|
>
|
||||||
|
{`Delete ${currentOrg?.name}`}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteOrg.isOpen}
|
isOpen={popUp.deleteOrg.isOpen}
|
||||||
title="Are you sure want to delete this organization?"
|
title="Are you sure want to delete this organization?"
|
||||||
@@ -65,6 +68,6 @@ export const OrgDeleteSection = () => {
|
|||||||
deleteKey="confirm"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={handleDeleteOrgSubmit}
|
onDeleteApproved={handleDeleteOrgSubmit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-4
@@ -3,15 +3,12 @@ import { useOrgPermission } from "@app/context";
|
|||||||
import { OrgDeleteSection } from "../OrgDeleteSection";
|
import { OrgDeleteSection } from "../OrgDeleteSection";
|
||||||
import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection";
|
import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection";
|
||||||
import { OrgNameChangeSection } from "../OrgNameChangeSection";
|
import { OrgNameChangeSection } from "../OrgNameChangeSection";
|
||||||
import { OrgSlugChangeSection } from "../OrgSlugChangeSection";
|
|
||||||
|
|
||||||
export const OrgGeneralTab = () => {
|
export const OrgGeneralTab = () => {
|
||||||
const { membership } = useOrgPermission();
|
const { membership } = useOrgPermission();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
<OrgNameChangeSection />
|
<OrgNameChangeSection />
|
||||||
<OrgSlugChangeSection />
|
|
||||||
<OrgIncidentContactsSection />
|
<OrgIncidentContactsSection />
|
||||||
{membership && membership.role === "admin" && <OrgDeleteSection />}
|
{membership && membership.role === "admin" && <OrgDeleteSection />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+12
-9
@@ -18,9 +18,10 @@ export const OrgIncidentContactsSection = () => {
|
|||||||
const { permission } = useOrgPermission();
|
const { permission } = useOrgPermission();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
<>
|
||||||
<div className="flex justify-between mb-4">
|
<hr className="border-mineshaft-600" />
|
||||||
<p className="min-w-max text-xl font-semibold">{t("section.incident.incident-contacts")}</p>
|
<div className="flex items-center justify-between pt-4">
|
||||||
|
<p className="text-md text-mineshaft-100">{t("section.incident.incident-contacts")}</p>
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
|
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
@@ -35,16 +36,18 @@ export const OrgIncidentContactsSection = () => {
|
|||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
{permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount) ? (
|
<div className="py-4">
|
||||||
<OrgIncidentContactsTable />
|
{permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount) ? (
|
||||||
) : (
|
<OrgIncidentContactsTable />
|
||||||
<PermissionDeniedBanner />
|
) : (
|
||||||
)}
|
<PermissionDeniedBanner />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<AddOrgIncidentContactModal
|
<AddOrgIncidentContactModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpClose={handlePopUpClose}
|
handlePopUpClose={handlePopUpClose}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+37
-19
@@ -10,11 +10,12 @@ import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@a
|
|||||||
import { useUpdateOrg } from "@app/hooks/api";
|
import { useUpdateOrg } from "@app/hooks/api";
|
||||||
|
|
||||||
const formSchema = yup.object({
|
const formSchema = yup.object({
|
||||||
name: yup
|
name: yup.string().required().label("Organization Name").max(64, "Too long, maximum length is 64 characters"),
|
||||||
.string()
|
slug: yup
|
||||||
.required()
|
.string()
|
||||||
.label("Organization Name")
|
.matches(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
|
||||||
.max(64, "Too long, maximum length is 64 characters")
|
.required()
|
||||||
|
.label("Organization Slug")
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = yup.InferType<typeof formSchema>;
|
type FormData = yup.InferType<typeof formSchema>;
|
||||||
@@ -29,40 +30,44 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentOrg) {
|
if (currentOrg) {
|
||||||
reset({ name: currentOrg.name });
|
reset({
|
||||||
|
name: currentOrg.name,
|
||||||
|
slug: currentOrg.slug
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [currentOrg]);
|
}, [currentOrg]);
|
||||||
|
|
||||||
const onFormSubmit = async ({ name }: FormData) => {
|
const onFormSubmit = async ({ name, slug }: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (!currentOrg?.id) return;
|
if (!currentOrg?.id) return;
|
||||||
if (name === "") return;
|
|
||||||
|
|
||||||
await mutateAsync({ orgId: currentOrg?.id, name });
|
await mutateAsync({
|
||||||
|
orgId: currentOrg?.id,
|
||||||
|
name,
|
||||||
|
slug
|
||||||
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully renamed organization",
|
text: "Successfully updated organization details",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Failed to rename organization",
|
text: "Failed to update organization details",
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
<div className="">
|
||||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
<h2 className="mb-2 text-md text-mineshaft-100">Organization Name</h2>
|
||||||
>
|
|
||||||
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Organization Name</p>
|
|
||||||
<div className="mb-2 max-w-md">
|
|
||||||
<Controller
|
<Controller
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
|
||||||
<Input placeholder="Acme Corp" {...field} />
|
<Input placeholder="Acme Corp" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -70,6 +75,19 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
name="name"
|
name="name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="py-4">
|
||||||
|
<h2 className="mb-2 text-md text-mineshaft-100">Organization Slug</h2>
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
|
||||||
|
<Input placeholder="acme" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="slug"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
@@ -85,4 +103,4 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
-88
@@ -1,88 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
|
||||||
import * as yup from "yup";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
|
||||||
import { useUpdateOrg } from "@app/hooks/api";
|
|
||||||
|
|
||||||
const formSchema = yup.object({
|
|
||||||
slug: yup
|
|
||||||
.string()
|
|
||||||
.matches(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
|
|
||||||
.required()
|
|
||||||
.label("Project Slug")
|
|
||||||
});
|
|
||||||
|
|
||||||
type FormData = yup.InferType<typeof formSchema>;
|
|
||||||
|
|
||||||
export const OrgSlugChangeSection = (): JSX.Element => {
|
|
||||||
const { currentOrg } = useOrganization();
|
|
||||||
const { createNotification } = useNotificationContext();
|
|
||||||
const { handleSubmit, control, reset } = useForm<FormData>({
|
|
||||||
resolver: yupResolver(formSchema)
|
|
||||||
});
|
|
||||||
const { mutateAsync, isLoading } = useUpdateOrg();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentOrg) {
|
|
||||||
reset({ slug: currentOrg.slug });
|
|
||||||
}
|
|
||||||
}, [currentOrg]);
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ slug }: FormData) => {
|
|
||||||
try {
|
|
||||||
if (!currentOrg?.id) return;
|
|
||||||
if (slug === "") return;
|
|
||||||
|
|
||||||
await mutateAsync({ orgId: currentOrg?.id, slug });
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully updated organization slug",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to update organization slug",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
|
||||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
|
||||||
>
|
|
||||||
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Organization Slug</p>
|
|
||||||
<div className="mb-2 max-w-md">
|
|
||||||
<Controller
|
|
||||||
defaultValue=""
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
|
||||||
<Input placeholder="acme" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
control={control}
|
|
||||||
name="slug"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Button
|
|
||||||
isLoading={isLoading}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="outline_bg"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { OrgSlugChangeSection } from "./OrgSlugChangeSection";
|
|
||||||
@@ -6,7 +6,7 @@ import { OrgGeneralTab } from "../OrgGeneralTab";
|
|||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "General", key: "tab-org-general" },
|
{ name: "General", key: "tab-org-general" },
|
||||||
{ name: "Authentication", key: "tab-org-auth" }
|
{ name: "Security", key: "tab-org-security" }
|
||||||
];
|
];
|
||||||
export const OrgTabGroup = () => {
|
export const OrgTabGroup = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user