mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-01 00:06:38 +00:00
refactor is licensed and subscribed util functions
This commit is contained in:
@@ -107,6 +107,11 @@ export class Config {
|
|||||||
process.env.MAXMIND_ASN_PATH = parsedConfig.server.maxmind_asn_path;
|
process.env.MAXMIND_ASN_PATH = parsedConfig.server.maxmind_asn_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process.env.DISABLE_ENTERPRISE_FEATURES = parsedConfig.flags
|
||||||
|
?.disable_enterprise_features
|
||||||
|
? "true"
|
||||||
|
: "false";
|
||||||
|
|
||||||
this.rawConfig = parsedConfig;
|
this.rawConfig = parsedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> {
|
export async function isLicensedOrSubscribed(
|
||||||
|
orgId: string,
|
||||||
|
tiers: string[]
|
||||||
|
): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
export async function isSubscribed(orgId: string): Promise<boolean> {
|
export async function isSubscribed(
|
||||||
|
orgId: string,
|
||||||
|
tiers: string[]
|
||||||
|
): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,7 +331,8 @@ export const configSchema = z
|
|||||||
disable_local_sites: z.boolean().optional(),
|
disable_local_sites: z.boolean().optional(),
|
||||||
disable_basic_wireguard_sites: z.boolean().optional(),
|
disable_basic_wireguard_sites: z.boolean().optional(),
|
||||||
disable_config_managed_domains: z.boolean().optional(),
|
disable_config_managed_domains: z.boolean().optional(),
|
||||||
disable_product_help_banners: z.boolean().optional()
|
disable_product_help_banners: z.boolean().optional(),
|
||||||
|
disable_enterprise_features: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
dns: z
|
dns: z
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ export async function checkOrgAccessPolicy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: check that the org is subscribed
|
||||||
|
|
||||||
// get the needed data
|
// get the needed data
|
||||||
|
|
||||||
if (!props.org) {
|
if (!props.org) {
|
||||||
|
|||||||
@@ -13,16 +13,18 @@
|
|||||||
|
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import license from "#private/license/license";
|
import license from "#private/license/license";
|
||||||
import { getOrgTierData } from "#private/lib/billing";
|
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||||
|
|
||||||
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> {
|
export async function isLicensedOrSubscribed(
|
||||||
|
orgId: string,
|
||||||
|
tiers: string[]
|
||||||
|
): Promise<boolean> {
|
||||||
if (build === "enterprise") {
|
if (build === "enterprise") {
|
||||||
return await license.isUnlocked();
|
return await license.isUnlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build === "saas") {
|
if (build === "saas") {
|
||||||
const { tier, active } = await getOrgTierData(orgId);
|
return isSubscribed(orgId, tiers);
|
||||||
return (tier == "tier1" || tier == "tier2" || tier == "tier3") && active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -14,10 +14,14 @@
|
|||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { getOrgTierData } from "#private/lib/billing";
|
import { getOrgTierData } from "#private/lib/billing";
|
||||||
|
|
||||||
export async function isSubscribed(orgId: string): Promise<boolean> {
|
export async function isSubscribed(
|
||||||
|
orgId: string,
|
||||||
|
tiers: string[]
|
||||||
|
): Promise<boolean> {
|
||||||
if (build === "saas") {
|
if (build === "saas") {
|
||||||
const { tier, active } = await getOrgTierData(orgId);
|
const { tier, active } = await getOrgTierData(orgId);
|
||||||
return (tier == "tier1" || tier == "tier2" || tier == "tier3") && active;
|
const isTier = (tier && tiers.includes(tier)) || false;
|
||||||
|
return active && isTier;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -17,44 +17,51 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { getOrgTierData } from "#private/lib/billing";
|
import { getOrgTierData } from "#private/lib/billing";
|
||||||
|
|
||||||
export async function verifyValidSubscription(
|
export function verifyValidSubscription(tiers: string[]) {
|
||||||
req: Request,
|
return async function (
|
||||||
res: Response,
|
req: Request,
|
||||||
next: NextFunction
|
res: Response,
|
||||||
) {
|
next: NextFunction
|
||||||
try {
|
): Promise<any> {
|
||||||
if (build != "saas") {
|
try {
|
||||||
|
if (build != "saas") {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgId =
|
||||||
|
req.params.orgId ||
|
||||||
|
req.body.orgId ||
|
||||||
|
req.query.orgId ||
|
||||||
|
req.userOrgId;
|
||||||
|
|
||||||
|
if (!orgId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization ID is required to verify subscription"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tier, active } = await getOrgTierData(orgId);
|
||||||
|
const isTier = tiers.includes(tier || "");
|
||||||
|
if (!isTier || !active) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"Organization does not have an active subscription"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
} catch (e) {
|
||||||
|
|
||||||
const orgId = req.params.orgId || req.body.orgId || req.query.orgId || req.userOrgId;
|
|
||||||
|
|
||||||
if (!orgId) {
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
"Organization ID is required to verify subscription"
|
"Error verifying subscription"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
const { tier, active } = await getOrgTierData(orgId);
|
|
||||||
if ((tier == "tier1" || tier == "tier2" || tier == "tier3") && active) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.FORBIDDEN,
|
|
||||||
"Organization does not have an active subscription"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (e) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Error verifying subscription"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ authenticated.put(
|
|||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/org/:orgId/idp/:idpId/oidc",
|
"/org/:orgId/idp/:idpId/oidc",
|
||||||
verifyValidLicense,
|
verifyValidLicense,
|
||||||
verifyValidSubscription,
|
verifyValidSubscription(),
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyIdpAccess,
|
verifyIdpAccess,
|
||||||
verifyUserHasAction(ActionsEnum.updateIdp),
|
verifyUserHasAction(ActionsEnum.updateIdp),
|
||||||
|
|||||||
@@ -195,27 +195,29 @@ export default function CredentialsPage() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
<Button
|
<SettingsSectionFooter>
|
||||||
variant="outline"
|
<Button
|
||||||
onClick={() => {
|
variant="outline"
|
||||||
setShouldDisconnect(false);
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setShouldDisconnect(false);
|
||||||
}}
|
setModalOpen(true);
|
||||||
disabled={isSecurityFeatureDisabled()}
|
}}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("regenerateCredentialsButton")}
|
>
|
||||||
</Button>
|
{t("regenerateCredentialsButton")}
|
||||||
<Button
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
setShouldDisconnect(true);
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setShouldDisconnect(true);
|
||||||
}}
|
setModalOpen(true);
|
||||||
disabled={isSecurityFeatureDisabled()}
|
}}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("remoteExitNodeRegenerateAndDisconnect")}
|
>
|
||||||
</Button>
|
{t("remoteExitNodeRegenerateAndDisconnect")}
|
||||||
</SettingsSectionFooter>
|
</Button>
|
||||||
|
</SettingsSectionFooter>
|
||||||
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|
||||||
|
|||||||
@@ -183,27 +183,29 @@ export default function CredentialsPage() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
<Button
|
<SettingsSectionFooter>
|
||||||
variant="outline"
|
<Button
|
||||||
onClick={() => {
|
variant="outline"
|
||||||
setShouldDisconnect(false);
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setShouldDisconnect(false);
|
||||||
}}
|
setModalOpen(true);
|
||||||
disabled={isSecurityFeatureDisabled()}
|
}}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("regenerateCredentialsButton")}
|
>
|
||||||
</Button>
|
{t("regenerateCredentialsButton")}
|
||||||
<Button
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
setShouldDisconnect(true);
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setShouldDisconnect(true);
|
||||||
}}
|
setModalOpen(true);
|
||||||
disabled={isSecurityFeatureDisabled()}
|
}}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("clientRegenerateAndDisconnect")}
|
>
|
||||||
</Button>
|
{t("clientRegenerateAndDisconnect")}
|
||||||
</SettingsSectionFooter>
|
</Button>
|
||||||
|
</SettingsSectionFooter>
|
||||||
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<OlmInstallCommands
|
<OlmInstallCommands
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ export default function GeneralPage() {
|
|||||||
const [approvalId, setApprovalId] = useState<number | null>(null);
|
const [approvalId, setApprovalId] = useState<number | null>(null);
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const [, startTransition] = useTransition();
|
const [, startTransition] = useTransition();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
const showApprovalFeatures = build !== "oss" && isPaidUser;
|
const showApprovalFeatures = build !== "oss" && isPaidUser;
|
||||||
|
|
||||||
@@ -567,231 +568,246 @@ export default function GeneralPage() {
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingsSection>
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
<SettingsSectionHeader>
|
<SettingsSection>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionHeader>
|
||||||
{t("deviceSecurity")}
|
<SettingsSectionTitle>
|
||||||
</SettingsSectionTitle>
|
{t("deviceSecurity")}
|
||||||
<SettingsSectionDescription>
|
</SettingsSectionTitle>
|
||||||
{t("deviceSecurityDescription")}
|
<SettingsSectionDescription>
|
||||||
</SettingsSectionDescription>
|
{t("deviceSecurityDescription")}
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionDescription>
|
||||||
|
</SettingsSectionHeader>
|
||||||
|
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<PaidFeaturesAlert />
|
<PaidFeaturesAlert />
|
||||||
{client.posture &&
|
{client.posture &&
|
||||||
Object.keys(client.posture).length > 0 ? (
|
Object.keys(client.posture).length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<InfoSections cols={3}>
|
<InfoSections cols={3}>
|
||||||
{client.posture.biometricsEnabled !== null &&
|
{client.posture.biometricsEnabled !==
|
||||||
client.posture.biometricsEnabled !==
|
null &&
|
||||||
undefined && (
|
client.posture.biometricsEnabled !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("biometricsEnabled")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t("biometricsEnabled")}
|
||||||
<InfoSectionContent>
|
</InfoSectionTitle>
|
||||||
{isPaidUser
|
<InfoSectionContent>
|
||||||
? formatPostureValue(
|
{isPaidUser
|
||||||
client.posture
|
? formatPostureValue(
|
||||||
.biometricsEnabled
|
client.posture
|
||||||
)
|
.biometricsEnabled
|
||||||
: "-"}
|
)
|
||||||
</InfoSectionContent>
|
: "-"}
|
||||||
</InfoSection>
|
</InfoSectionContent>
|
||||||
)}
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{client.posture.diskEncrypted !== null &&
|
{client.posture.diskEncrypted !== null &&
|
||||||
client.posture.diskEncrypted !==
|
client.posture.diskEncrypted !==
|
||||||
undefined && (
|
undefined && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("diskEncrypted")}
|
{t("diskEncrypted")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
{isPaidUser
|
{isPaidUser
|
||||||
? formatPostureValue(
|
? formatPostureValue(
|
||||||
client.posture
|
client.posture
|
||||||
.diskEncrypted
|
.diskEncrypted
|
||||||
)
|
)
|
||||||
: "-"}
|
: "-"}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{client.posture.firewallEnabled !== null &&
|
{client.posture.firewallEnabled !== null &&
|
||||||
client.posture.firewallEnabled !==
|
client.posture.firewallEnabled !==
|
||||||
undefined && (
|
undefined && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("firewallEnabled")}
|
{t("firewallEnabled")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
{isPaidUser
|
{isPaidUser
|
||||||
? formatPostureValue(
|
? formatPostureValue(
|
||||||
client.posture
|
client.posture
|
||||||
.firewallEnabled
|
.firewallEnabled
|
||||||
)
|
)
|
||||||
: "-"}
|
: "-"}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{client.posture.autoUpdatesEnabled !== null &&
|
{client.posture.autoUpdatesEnabled !==
|
||||||
client.posture.autoUpdatesEnabled !==
|
null &&
|
||||||
undefined && (
|
client.posture.autoUpdatesEnabled !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("autoUpdatesEnabled")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t("autoUpdatesEnabled")}
|
||||||
<InfoSectionContent>
|
</InfoSectionTitle>
|
||||||
{isPaidUser
|
<InfoSectionContent>
|
||||||
? formatPostureValue(
|
{isPaidUser
|
||||||
client.posture
|
? formatPostureValue(
|
||||||
.autoUpdatesEnabled
|
client.posture
|
||||||
)
|
.autoUpdatesEnabled
|
||||||
: "-"}
|
)
|
||||||
</InfoSectionContent>
|
: "-"}
|
||||||
</InfoSection>
|
</InfoSectionContent>
|
||||||
)}
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{client.posture.tpmAvailable !== null &&
|
{client.posture.tpmAvailable !== null &&
|
||||||
client.posture.tpmAvailable !==
|
client.posture.tpmAvailable !==
|
||||||
undefined && (
|
undefined && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("tpmAvailable")}
|
{t("tpmAvailable")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
{isPaidUser
|
{isPaidUser
|
||||||
? formatPostureValue(
|
? formatPostureValue(
|
||||||
client.posture
|
client.posture
|
||||||
.tpmAvailable
|
.tpmAvailable
|
||||||
)
|
)
|
||||||
: "-"}
|
: "-"}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{client.posture.windowsAntivirusEnabled !==
|
{client.posture.windowsAntivirusEnabled !==
|
||||||
null &&
|
null &&
|
||||||
client.posture.windowsAntivirusEnabled !==
|
client.posture
|
||||||
undefined && (
|
.windowsAntivirusEnabled !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("windowsAntivirusEnabled")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t(
|
||||||
<InfoSectionContent>
|
"windowsAntivirusEnabled"
|
||||||
{isPaidUser
|
)}
|
||||||
? formatPostureValue(
|
</InfoSectionTitle>
|
||||||
client.posture
|
<InfoSectionContent>
|
||||||
.windowsAntivirusEnabled
|
{isPaidUser
|
||||||
)
|
? formatPostureValue(
|
||||||
: "-"}
|
client.posture
|
||||||
</InfoSectionContent>
|
.windowsAntivirusEnabled
|
||||||
</InfoSection>
|
)
|
||||||
)}
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{client.posture.macosSipEnabled !== null &&
|
{client.posture.macosSipEnabled !== null &&
|
||||||
client.posture.macosSipEnabled !==
|
client.posture.macosSipEnabled !==
|
||||||
undefined && (
|
undefined && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("macosSipEnabled")}
|
{t("macosSipEnabled")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
{isPaidUser
|
{isPaidUser
|
||||||
? formatPostureValue(
|
? formatPostureValue(
|
||||||
client.posture
|
client.posture
|
||||||
.macosSipEnabled
|
.macosSipEnabled
|
||||||
)
|
)
|
||||||
: "-"}
|
: "-"}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{client.posture.macosGatekeeperEnabled !==
|
{client.posture.macosGatekeeperEnabled !==
|
||||||
null &&
|
null &&
|
||||||
client.posture.macosGatekeeperEnabled !==
|
client.posture
|
||||||
undefined && (
|
.macosGatekeeperEnabled !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("macosGatekeeperEnabled")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t(
|
||||||
<InfoSectionContent>
|
"macosGatekeeperEnabled"
|
||||||
{isPaidUser
|
)}
|
||||||
? formatPostureValue(
|
</InfoSectionTitle>
|
||||||
client.posture
|
<InfoSectionContent>
|
||||||
.macosGatekeeperEnabled
|
{isPaidUser
|
||||||
)
|
? formatPostureValue(
|
||||||
: "-"}
|
client.posture
|
||||||
</InfoSectionContent>
|
.macosGatekeeperEnabled
|
||||||
</InfoSection>
|
)
|
||||||
)}
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{client.posture.macosFirewallStealthMode !==
|
{client.posture.macosFirewallStealthMode !==
|
||||||
null &&
|
null &&
|
||||||
client.posture.macosFirewallStealthMode !==
|
client.posture
|
||||||
undefined && (
|
.macosFirewallStealthMode !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("macosFirewallStealthMode")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t(
|
||||||
<InfoSectionContent>
|
"macosFirewallStealthMode"
|
||||||
{isPaidUser
|
)}
|
||||||
? formatPostureValue(
|
</InfoSectionTitle>
|
||||||
client.posture
|
<InfoSectionContent>
|
||||||
.macosFirewallStealthMode
|
{isPaidUser
|
||||||
)
|
? formatPostureValue(
|
||||||
: "-"}
|
client.posture
|
||||||
</InfoSectionContent>
|
.macosFirewallStealthMode
|
||||||
</InfoSection>
|
)
|
||||||
)}
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{client.posture.linuxAppArmorEnabled !== null &&
|
{client.posture.linuxAppArmorEnabled !==
|
||||||
client.posture.linuxAppArmorEnabled !==
|
null &&
|
||||||
undefined && (
|
client.posture.linuxAppArmorEnabled !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("linuxAppArmorEnabled")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t("linuxAppArmorEnabled")}
|
||||||
<InfoSectionContent>
|
</InfoSectionTitle>
|
||||||
{isPaidUser
|
<InfoSectionContent>
|
||||||
? formatPostureValue(
|
{isPaidUser
|
||||||
client.posture
|
? formatPostureValue(
|
||||||
.linuxAppArmorEnabled
|
client.posture
|
||||||
)
|
.linuxAppArmorEnabled
|
||||||
: "-"}
|
)
|
||||||
</InfoSectionContent>
|
: "-"}
|
||||||
</InfoSection>
|
</InfoSectionContent>
|
||||||
)}
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
{client.posture.linuxSELinuxEnabled !== null &&
|
{client.posture.linuxSELinuxEnabled !==
|
||||||
client.posture.linuxSELinuxEnabled !==
|
null &&
|
||||||
undefined && (
|
client.posture.linuxSELinuxEnabled !==
|
||||||
<InfoSection>
|
undefined && (
|
||||||
<InfoSectionTitle>
|
<InfoSection>
|
||||||
{t("linuxSELinuxEnabled")}
|
<InfoSectionTitle>
|
||||||
</InfoSectionTitle>
|
{t("linuxSELinuxEnabled")}
|
||||||
<InfoSectionContent>
|
</InfoSectionTitle>
|
||||||
{isPaidUser
|
<InfoSectionContent>
|
||||||
? formatPostureValue(
|
{isPaidUser
|
||||||
client.posture
|
? formatPostureValue(
|
||||||
.linuxSELinuxEnabled
|
client.posture
|
||||||
)
|
.linuxSELinuxEnabled
|
||||||
: "-"}
|
)
|
||||||
</InfoSectionContent>
|
: "-"}
|
||||||
</InfoSection>
|
</InfoSectionContent>
|
||||||
)}
|
</InfoSection>
|
||||||
</InfoSections>
|
)}
|
||||||
</>
|
</InfoSections>
|
||||||
) : (
|
</>
|
||||||
<div className="text-muted-foreground">
|
) : (
|
||||||
{t("noData")}
|
<div className="text-muted-foreground">
|
||||||
</div>
|
{t("noData")}
|
||||||
)}
|
</div>
|
||||||
</SettingsSectionBody>
|
)}
|
||||||
</SettingsSection>
|
</SettingsSectionBody>
|
||||||
|
</SettingsSection>
|
||||||
|
)}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
||||||
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
|
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
type GeneralSettingsProps = {
|
type GeneralSettingsProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -23,6 +24,7 @@ export default async function GeneralSettingsPage({
|
|||||||
const { orgId } = await params;
|
const { orgId } = await params;
|
||||||
|
|
||||||
const user = await verifySession();
|
const user = await verifySession();
|
||||||
|
const env = pullEnv();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(`/`);
|
redirect(`/`);
|
||||||
@@ -56,10 +58,15 @@ export default async function GeneralSettingsPage({
|
|||||||
title: t("security"),
|
title: t("security"),
|
||||||
href: `/{orgId}/settings/general/security`
|
href: `/{orgId}/settings/general/security`
|
||||||
},
|
},
|
||||||
{
|
// PaidFeaturesAlert
|
||||||
title: t("authPage"),
|
...(!env.flags.disableEnterpriseFeatures
|
||||||
href: `/{orgId}/settings/general/auth-page`
|
? [
|
||||||
}
|
{
|
||||||
|
title: t("authPage"),
|
||||||
|
href: `/{orgId}/settings/general/auth-page`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -102,10 +102,13 @@ type SectionFormProps = {
|
|||||||
|
|
||||||
export default function SecurityPage() {
|
export default function SecurityPage() {
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
|
const { env } = useEnvContext();
|
||||||
return (
|
return (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<LogRetentionSectionForm org={org.org} />
|
<LogRetentionSectionForm org={org.org} />
|
||||||
<SecuritySettingsSectionForm org={org.org} />
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
|
<SecuritySettingsSectionForm org={org.org} />
|
||||||
|
)}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -135,7 +138,8 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
const { isPaidUser, hasSaasSubscription } = usePaidStatus();
|
const { isPaidUser, hasSaasSubscription } = usePaidStatus();
|
||||||
|
|
||||||
const [, formAction, loadingSave] = useActionState(performSave, null);
|
const [, formAction, loadingSave] = useActionState(performSave, null);
|
||||||
const api = createApiClient(useEnvContext());
|
const { env } = useEnvContext();
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
|
||||||
async function performSave() {
|
async function performSave() {
|
||||||
const isValid = await form.trigger();
|
const isValid = await form.trigger();
|
||||||
@@ -238,120 +242,144 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PaidFeaturesAlert />
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
|
<>
|
||||||
|
<PaidFeaturesAlert />
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="settingsLogRetentionDaysAccess"
|
name="settingsLogRetentionDaysAccess"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
const isDisabled = !isPaidUser;
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("logRetentionAccessLabel")}
|
{t(
|
||||||
</FormLabel>
|
"logRetentionAccessLabel"
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={field.value.toString()}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
if (!isDisabled) {
|
|
||||||
field.onChange(
|
|
||||||
parseInt(
|
|
||||||
value,
|
|
||||||
10
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isDisabled}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectLogRetention"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{LOG_RETENTION_OPTIONS.map(
|
|
||||||
(option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={
|
|
||||||
option.value
|
|
||||||
}
|
|
||||||
value={option.value.toString()}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
option.label
|
|
||||||
)}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</SelectContent>
|
</FormLabel>
|
||||||
</Select>
|
<FormControl>
|
||||||
</FormControl>
|
<Select
|
||||||
<FormMessage />
|
value={field.value.toString()}
|
||||||
</FormItem>
|
onValueChange={(
|
||||||
);
|
value
|
||||||
}}
|
) => {
|
||||||
/>
|
if (
|
||||||
<FormField
|
!isDisabled
|
||||||
control={form.control}
|
) {
|
||||||
name="settingsLogRetentionDaysAction"
|
field.onChange(
|
||||||
render={({ field }) => {
|
parseInt(
|
||||||
const isDisabled = !isPaidUser;
|
value,
|
||||||
|
10
|
||||||
return (
|
)
|
||||||
<FormItem>
|
);
|
||||||
<FormLabel>
|
}
|
||||||
{t("logRetentionActionLabel")}
|
}}
|
||||||
</FormLabel>
|
disabled={
|
||||||
<FormControl>
|
isDisabled
|
||||||
<Select
|
}
|
||||||
value={field.value.toString()}
|
>
|
||||||
onValueChange={(value) => {
|
<SelectTrigger>
|
||||||
if (!isDisabled) {
|
<SelectValue
|
||||||
field.onChange(
|
placeholder={t(
|
||||||
parseInt(
|
"selectLogRetention"
|
||||||
value,
|
|
||||||
10
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isDisabled}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectLogRetention"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{LOG_RETENTION_OPTIONS.map(
|
|
||||||
(option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={
|
|
||||||
option.value
|
|
||||||
}
|
|
||||||
value={option.value.toString()}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
option.label
|
|
||||||
)}
|
)}
|
||||||
</SelectItem>
|
/>
|
||||||
)
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{LOG_RETENTION_OPTIONS.map(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.value
|
||||||
|
}
|
||||||
|
value={option.value.toString()}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
option.label
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="settingsLogRetentionDaysAction"
|
||||||
|
render={({ field }) => {
|
||||||
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"logRetentionActionLabel"
|
||||||
)}
|
)}
|
||||||
</SelectContent>
|
</FormLabel>
|
||||||
</Select>
|
<FormControl>
|
||||||
</FormControl>
|
<Select
|
||||||
<FormMessage />
|
value={field.value.toString()}
|
||||||
</FormItem>
|
onValueChange={(
|
||||||
);
|
value
|
||||||
}}
|
) => {
|
||||||
/>
|
if (
|
||||||
|
!isDisabled
|
||||||
|
) {
|
||||||
|
field.onChange(
|
||||||
|
parseInt(
|
||||||
|
value,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
isDisabled
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"selectLogRetention"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{LOG_RETENTION_OPTIONS.map(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.value
|
||||||
|
}
|
||||||
|
value={option.value.toString()}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
option.label
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
|
|||||||
@@ -163,7 +163,9 @@ function MaintenanceSectionForm({
|
|||||||
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
|
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
|
||||||
const isSaasNotSubscribed =
|
const isSaasNotSubscribed =
|
||||||
build === "saas" && !subscription?.isSubscribed();
|
build === "saas" && !subscription?.isSubscribed();
|
||||||
return isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss";
|
return (
|
||||||
|
isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!resource.http) {
|
if (!resource.http) {
|
||||||
@@ -189,13 +191,14 @@ function MaintenanceSectionForm({
|
|||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
id="maintenance-settings-form"
|
id="maintenance-settings-form"
|
||||||
>
|
>
|
||||||
<PaidFeaturesAlert></PaidFeaturesAlert>
|
<PaidFeaturesAlert />
|
||||||
<FormField
|
<FormField
|
||||||
control={maintenanceForm.control}
|
control={maintenanceForm.control}
|
||||||
name="maintenanceModeEnabled"
|
name="maintenanceModeEnabled"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
const isDisabled =
|
const isDisabled =
|
||||||
isSecurityFeatureDisabled() || resource.http === false;
|
isSecurityFeatureDisabled() ||
|
||||||
|
resource.http === false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
@@ -415,7 +418,7 @@ function MaintenanceSectionForm({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={maintenanceSaveLoading}
|
loading={maintenanceSaveLoading}
|
||||||
disabled={maintenanceSaveLoading || !isPaidUser }
|
disabled={maintenanceSaveLoading || !isPaidUser}
|
||||||
form="maintenance-settings-form"
|
form="maintenance-settings-form"
|
||||||
>
|
>
|
||||||
{t("saveSettings")}
|
{t("saveSettings")}
|
||||||
@@ -741,10 +744,12 @@ export default function GeneralForm() {
|
|||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<MaintenanceSectionForm
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
resource={resource}
|
<MaintenanceSectionForm
|
||||||
updateResource={updateResource}
|
resource={resource}
|
||||||
/>
|
updateResource={updateResource}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|
||||||
<Credenza
|
<Credenza
|
||||||
|
|||||||
@@ -271,27 +271,29 @@ export default function CredentialsPage() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
<Button
|
<SettingsSectionFooter>
|
||||||
variant="outline"
|
<Button
|
||||||
onClick={() => {
|
variant="outline"
|
||||||
setShouldDisconnect(false);
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setShouldDisconnect(false);
|
||||||
}}
|
setModalOpen(true);
|
||||||
disabled={isSecurityFeatureDisabled()}
|
}}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("regenerateCredentialsButton")}
|
>
|
||||||
</Button>
|
{t("regenerateCredentialsButton")}
|
||||||
<Button
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
setShouldDisconnect(true);
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setShouldDisconnect(true);
|
||||||
}}
|
setModalOpen(true);
|
||||||
disabled={isSecurityFeatureDisabled()}
|
}}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("siteRegenerateAndDisconnect")}
|
>
|
||||||
</Button>
|
{t("siteRegenerateAndDisconnect")}
|
||||||
</SettingsSectionFooter>
|
</Button>
|
||||||
|
</SettingsSectionFooter>
|
||||||
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<NewtSiteInstallCommands
|
<NewtSiteInstallCommands
|
||||||
@@ -383,14 +385,16 @@ export default function CredentialsPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
<Button
|
<SettingsSectionFooter>
|
||||||
onClick={() => setModalOpen(true)}
|
<Button
|
||||||
disabled={isSecurityFeatureDisabled()}
|
onClick={() => setModalOpen(true)}
|
||||||
>
|
disabled={isSecurityFeatureDisabled()}
|
||||||
{t("siteRegenerateAndDisconnect")}
|
>
|
||||||
</Button>
|
{t("siteRegenerateAndDisconnect")}
|
||||||
</SettingsSectionFooter>
|
</Button>
|
||||||
|
</SettingsSectionFooter>
|
||||||
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|||||||
@@ -121,16 +121,27 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
href: "/{orgId}/settings/access/roles",
|
href: "/{orgId}/settings/access/roles",
|
||||||
icon: <Users className="size-4 flex-none" />
|
icon: <Users className="size-4 flex-none" />
|
||||||
},
|
},
|
||||||
{
|
// PaidFeaturesAlert
|
||||||
title: "sidebarIdentityProviders",
|
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||||
href: "/{orgId}/settings/idp",
|
build === "saas" ||
|
||||||
icon: <Fingerprint className="size-4 flex-none" />
|
env?.flags.useOrgOnlyIdp
|
||||||
},
|
? [
|
||||||
{
|
{
|
||||||
title: "sidebarApprovals",
|
title: "sidebarIdentityProviders",
|
||||||
href: "/{orgId}/settings/access/approvals",
|
href: "/{orgId}/settings/idp",
|
||||||
icon: <UserCog className="size-4 flex-none" />
|
icon: <Fingerprint className="size-4 flex-none" />
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(!env?.flags.disableEnterpriseFeatures
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: "sidebarApprovals",
|
||||||
|
href: "/{orgId}/settings/access/approvals",
|
||||||
|
icon: <UserCog className="size-4 flex-none" />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
title: "sidebarShareableLinks",
|
title: "sidebarShareableLinks",
|
||||||
href: "/{orgId}/settings/share-links",
|
href: "/{orgId}/settings/share-links",
|
||||||
@@ -147,16 +158,20 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
href: "/{orgId}/settings/logs/request",
|
href: "/{orgId}/settings/logs/request",
|
||||||
icon: <SquareMousePointer className="size-4 flex-none" />
|
icon: <SquareMousePointer className="size-4 flex-none" />
|
||||||
},
|
},
|
||||||
{
|
...(!env?.flags.disableEnterpriseFeatures
|
||||||
title: "sidebarLogsAccess",
|
? [
|
||||||
href: "/{orgId}/settings/logs/access",
|
{
|
||||||
icon: <ScanEye className="size-4 flex-none" />
|
title: "sidebarLogsAccess",
|
||||||
},
|
href: "/{orgId}/settings/logs/access",
|
||||||
{
|
icon: <ScanEye className="size-4 flex-none" />
|
||||||
title: "sidebarLogsAction",
|
},
|
||||||
href: "/{orgId}/settings/logs/action",
|
{
|
||||||
icon: <Logs className="size-4 flex-none" />
|
title: "sidebarLogsAction",
|
||||||
}
|
href: "/{orgId}/settings/logs/action",
|
||||||
|
icon: <Logs className="size-4 flex-none" />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
];
|
];
|
||||||
|
|
||||||
const analytics = {
|
const analytics = {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export default function CreateRoleForm({
|
|||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { isPaidUser } = usePaidStatus();
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
@@ -161,50 +162,56 @@ export default function CreateRoleForm({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PaidFeaturesAlert />
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
|
<>
|
||||||
|
<PaidFeaturesAlert />
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="requireDeviceApproval"
|
name="requireDeviceApproval"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="my-2">
|
<FormItem className="my-2">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<CheckboxWithLabel
|
<CheckboxWithLabel
|
||||||
{...field}
|
{...field}
|
||||||
disabled={!isPaidUser}
|
disabled={
|
||||||
value="on"
|
!isPaidUser
|
||||||
checked={form.watch(
|
}
|
||||||
"requireDeviceApproval"
|
value="on"
|
||||||
)}
|
checked={form.watch(
|
||||||
onCheckedChange={(
|
"requireDeviceApproval"
|
||||||
checked
|
)}
|
||||||
) => {
|
onCheckedChange={(
|
||||||
if (
|
|
||||||
checked !==
|
|
||||||
"indeterminate"
|
|
||||||
) {
|
|
||||||
form.setValue(
|
|
||||||
"requireDeviceApproval",
|
|
||||||
checked
|
checked
|
||||||
);
|
) => {
|
||||||
}
|
if (
|
||||||
}}
|
checked !==
|
||||||
label={t(
|
"indeterminate"
|
||||||
"requireDeviceApproval"
|
) {
|
||||||
)}
|
form.setValue(
|
||||||
/>
|
"requireDeviceApproval",
|
||||||
</FormControl>
|
checked
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
label={t(
|
||||||
|
"requireDeviceApproval"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t(
|
{t(
|
||||||
"requireDeviceApprovalDescription"
|
"requireDeviceApprovalDescription"
|
||||||
)}
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default function EditRoleForm({
|
|||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { isPaidUser } = usePaidStatus();
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
@@ -168,50 +169,57 @@ export default function EditRoleForm({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<PaidFeaturesAlert />
|
|
||||||
|
|
||||||
<FormField
|
{!env.flags.disableEnterpriseFeatures && (
|
||||||
control={form.control}
|
<>
|
||||||
name="requireDeviceApproval"
|
<PaidFeaturesAlert />
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="my-2">
|
<FormField
|
||||||
<FormControl>
|
control={form.control}
|
||||||
<CheckboxWithLabel
|
name="requireDeviceApproval"
|
||||||
{...field}
|
render={({ field }) => (
|
||||||
disabled={!isPaidUser}
|
<FormItem className="my-2">
|
||||||
value="on"
|
<FormControl>
|
||||||
checked={form.watch(
|
<CheckboxWithLabel
|
||||||
"requireDeviceApproval"
|
{...field}
|
||||||
)}
|
disabled={
|
||||||
onCheckedChange={(
|
!isPaidUser
|
||||||
checked
|
}
|
||||||
) => {
|
value="on"
|
||||||
if (
|
checked={form.watch(
|
||||||
checked !==
|
"requireDeviceApproval"
|
||||||
"indeterminate"
|
)}
|
||||||
) {
|
onCheckedChange={(
|
||||||
form.setValue(
|
|
||||||
"requireDeviceApproval",
|
|
||||||
checked
|
checked
|
||||||
);
|
) => {
|
||||||
}
|
if (
|
||||||
}}
|
checked !==
|
||||||
label={t(
|
"indeterminate"
|
||||||
"requireDeviceApproval"
|
) {
|
||||||
)}
|
form.setValue(
|
||||||
/>
|
"requireDeviceApproval",
|
||||||
</FormControl>
|
checked
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
label={t(
|
||||||
|
"requireDeviceApproval"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t(
|
{t(
|
||||||
"requireDeviceApprovalDescription"
|
"requireDeviceApprovalDescription"
|
||||||
)}
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
|||||||
import { ExternalLink, KeyRound, Sparkles } from "lucide-react";
|
import { ExternalLink, KeyRound, Sparkles } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
|
||||||
const bannerClassName =
|
const bannerClassName =
|
||||||
"mb-6 border-primary/30 bg-linear-to-br from-primary/10 via-background to-background overflow-hidden";
|
"mb-6 border-primary/30 bg-linear-to-br from-primary/10 via-background to-background overflow-hidden";
|
||||||
@@ -15,6 +16,12 @@ const bannerRowClassName =
|
|||||||
export function PaidFeaturesAlert() {
|
export function PaidFeaturesAlert() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { hasSaasSubscription, hasEnterpriseLicense } = usePaidStatus();
|
const { hasSaasSubscription, hasEnterpriseLicense } = usePaidStatus();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
|
if (env.flags.disableEnterpriseFeatures) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{build === "saas" && !hasSaasSubscription ? (
|
{build === "saas" && !hasSaasSubscription ? (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { createContext } from "react";
|
|||||||
type SubscriptionStatusContextType = {
|
type SubscriptionStatusContextType = {
|
||||||
subscriptionStatus: GetOrgSubscriptionResponse | null;
|
subscriptionStatus: GetOrgSubscriptionResponse | null;
|
||||||
updateSubscriptionStatus: (updatedSite: GetOrgSubscriptionResponse) => void;
|
updateSubscriptionStatus: (updatedSite: GetOrgSubscriptionResponse) => void;
|
||||||
isActive: () => boolean;
|
|
||||||
getTier: () => { tier: string | null; active: boolean };
|
getTier: () => { tier: string | null; active: boolean };
|
||||||
isSubscribed: () => boolean;
|
isSubscribed: () => boolean;
|
||||||
subscribed: boolean;
|
subscribed: boolean;
|
||||||
|
|||||||
@@ -8,14 +8,29 @@ export function usePaidStatus() {
|
|||||||
|
|
||||||
// Check if features are disabled due to licensing/subscription
|
// Check if features are disabled due to licensing/subscription
|
||||||
const hasEnterpriseLicense = build === "enterprise" && isUnlocked();
|
const hasEnterpriseLicense = build === "enterprise" && isUnlocked();
|
||||||
const hasSaasSubscription =
|
const tierData = subscription?.getTier();
|
||||||
build === "saas" &&
|
const hasSaasSubscription = build === "saas" && tierData?.active;
|
||||||
subscription?.isSubscribed() &&
|
|
||||||
subscription.isActive();
|
function isPaidUser(tiers: string[]): boolean {
|
||||||
|
if (hasEnterpriseLicense) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasSaasSubscription &&
|
||||||
|
tierData?.tier &&
|
||||||
|
tiers.includes(tierData.tier)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasEnterpriseLicense,
|
hasEnterpriseLicense,
|
||||||
hasSaasSubscription,
|
hasSaasSubscription,
|
||||||
isPaidUser: hasEnterpriseLicense || hasSaasSubscription
|
isPaidUser,
|
||||||
|
subscriptionTier: tierData?.tier
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,11 @@ export function pullEnv(): Env {
|
|||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
useOrgOnlyIdp:
|
useOrgOnlyIdp:
|
||||||
process.env.USE_ORG_ONLY_IDP === "true" ? true : false
|
process.env.USE_ORG_ONLY_IDP === "true" ? true : false,
|
||||||
|
disableEnterpriseFeatures:
|
||||||
|
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
|
||||||
|
? true
|
||||||
|
: false
|
||||||
},
|
},
|
||||||
|
|
||||||
branding: {
|
branding: {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type Env = {
|
|||||||
usePangolinDns: boolean;
|
usePangolinDns: boolean;
|
||||||
disableProductHelpBanners: boolean;
|
disableProductHelpBanners: boolean;
|
||||||
useOrgOnlyIdp: boolean;
|
useOrgOnlyIdp: boolean;
|
||||||
|
disableEnterpriseFeatures: boolean;
|
||||||
};
|
};
|
||||||
branding: {
|
branding: {
|
||||||
appName?: string;
|
appName?: string;
|
||||||
|
|||||||
@@ -31,16 +31,6 @@ export function SubscriptionStatusProvider({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isActive = () => {
|
|
||||||
if (subscriptionStatus?.subscriptions) {
|
|
||||||
// Check if any subscription is active
|
|
||||||
return subscriptionStatus.subscriptions.some(
|
|
||||||
(sub) => sub.subscription?.status === "active"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTier = () => {
|
const getTier = () => {
|
||||||
if (subscriptionStatus?.subscriptions) {
|
if (subscriptionStatus?.subscriptions) {
|
||||||
// Iterate through all subscriptions
|
// Iterate through all subscriptions
|
||||||
@@ -65,9 +55,6 @@ export function SubscriptionStatusProvider({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isSubscribed = () => {
|
const isSubscribed = () => {
|
||||||
if (build === "enterprise") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const { tier, active } = getTier();
|
const { tier, active } = getTier();
|
||||||
return (
|
return (
|
||||||
(tier == "tier1" || tier == "tier2" || tier == "tier3") &&
|
(tier == "tier1" || tier == "tier2" || tier == "tier3") &&
|
||||||
@@ -82,7 +69,6 @@ export function SubscriptionStatusProvider({
|
|||||||
value={{
|
value={{
|
||||||
subscriptionStatus: subscriptionStatusState,
|
subscriptionStatus: subscriptionStatusState,
|
||||||
updateSubscriptionStatus,
|
updateSubscriptionStatus,
|
||||||
isActive,
|
|
||||||
getTier,
|
getTier,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
subscribed
|
subscribed
|
||||||
|
|||||||
Reference in New Issue
Block a user