Merge remote-tracking branch 'origin/dev' into update-packages

This commit is contained in:
Lokowitz
2026-02-12 15:47:29 +00:00
174 changed files with 5718 additions and 3824 deletions

View File

@@ -19,6 +19,7 @@ import OrgPolicyResult from "@app/components/OrgPolicyResult";
import UserProvider from "@app/providers/UserProvider";
import { Layout } from "@app/components/Layout";
import ApplyInternalRedirect from "@app/components/ApplyInternalRedirect";
import SubscriptionViolation from "@app/components/SubscriptionViolation";
export default async function OrgLayout(props: {
children: React.ReactNode;
@@ -108,6 +109,7 @@ export default async function OrgLayout(props: {
>
<ApplyInternalRedirect orgId={orgId} />
{props.children}
{build === "saas" && <SubscriptionViolation />}
<SetLastOrgCookie orgId={orgId} />
</SubscriptionStatusProvider>
);

View File

@@ -11,6 +11,7 @@ import type { GetOrgResponse } from "@server/routers/org";
import type { ListRolesResponse } from "@server/routers/role";
import type { AxiosResponse } from "axios";
import { getTranslations } from "next-intl/server";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export interface ApprovalFeedPageProps {
params: Promise<{ orgId: string }>;
@@ -29,10 +30,9 @@ export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) {
// Fetch roles to check if approvals are enabled
let hasApprovalsEnabled = false;
const rolesRes = await internal
.get<AxiosResponse<ListRolesResponse>>(
`/org/${params.orgId}/roles`,
await authCookieHeader()
)
.get<
AxiosResponse<ListRolesResponse>
>(`/org/${params.orgId}/roles`, await authCookieHeader())
.catch((e) => {});
if (rolesRes && rolesRes.status === 200) {
@@ -52,7 +52,7 @@ export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) {
<ApprovalsBanner />
<PaidFeaturesAlert />
<PaidFeaturesAlert tiers={tierMatrix.deviceApprovals} />
<OrgProvider org={org}>
<div className="container mx-auto max-w-12xl">

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,6 @@ import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useState, useEffect } from "react";
import { SwitchInput } from "@app/components/SwitchInput";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon, ExternalLink } from "lucide-react";
import {
@@ -41,12 +40,13 @@ import {
InfoSectionTitle
} from "@app/components/InfoSection";
import CopyToClipboard from "@app/components/CopyToClipboard";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import IdpTypeBadge from "@app/components/IdpTypeBadge";
import { useTranslations } from "next-intl";
import { AxiosResponse } from "axios";
import { ListRolesResponse } from "@server/routers/role";
import AutoProvisionConfigWidget from "@app/components/private/AutoProvisionConfigWidget";
import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function GeneralPage() {
const { env } = useEnvContext();
@@ -60,7 +60,6 @@ export default function GeneralPage() {
"role" | "expression"
>("role");
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
const { isUnlocked } = useLicenseStatusContext();
const dashboardRedirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
const [redirectUrl, setRedirectUrl] = useState(
@@ -499,6 +498,10 @@ export default function GeneralPage() {
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm>
<PaidFeaturesAlert
tiers={tierMatrix.autoProvisioning}
/>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}

View File

@@ -1,6 +1,7 @@
"use client";
import AutoProvisionConfigWidget from "@app/components/private/AutoProvisionConfigWidget";
import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import {
SettingsContainer,
SettingsSection,
@@ -27,9 +28,11 @@ import {
import { Input } from "@app/components/ui/input";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { ListRolesResponse } from "@server/routers/role";
import { AxiosResponse } from "axios";
import { InfoIcon } from "lucide-react";
@@ -49,8 +52,8 @@ export default function Page() {
const [roleMappingMode, setRoleMappingMode] = useState<
"role" | "expression"
>("role");
const { isUnlocked } = useLicenseStatusContext();
const t = useTranslations();
const { isPaidUser } = usePaidStatus();
const params = useParams();
@@ -361,6 +364,9 @@ export default function Page() {
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm>
<PaidFeaturesAlert
tiers={tierMatrix.autoProvisioning}
/>
<Form {...form}>
<form
className="space-y-4"
@@ -806,7 +812,7 @@ export default function Page() {
</Button>
<Button
type="submit"
disabled={createLoading}
disabled={createLoading || !isPaidUser(tierMatrix.orgOidc)}
loading={createLoading}
onClick={() => {
// log any issues with the form

View File

@@ -1,18 +1,8 @@
import { pullEnv } from "@app/lib/pullEnv";
import { build } from "@server/build";
import { redirect } from "next/navigation";
interface LayoutProps {
children: React.ReactNode;
params: Promise<{}>;
}
export default async function Layout(props: LayoutProps) {
const env = pullEnv();
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
redirect("/");
}
return props.children;
}

View File

@@ -2,9 +2,11 @@ import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import IdpTable, { IdpRow } from "@app/components/private/OrgIdpTable";
import IdpTable, { IdpRow } from "@app/components/OrgIdpTable";
import { getTranslations } from "next-intl/server";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { IdpGlobalModeBanner } from "@app/components/IdpGlobalModeBanner";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
type OrgIdpPageProps = {
params: Promise<{ orgId: string }>;
@@ -35,7 +37,9 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
description={t("idpManageDescription")}
/>
<PaidFeaturesAlert />
<IdpGlobalModeBanner />
<PaidFeaturesAlert tiers={tierMatrix.orgOidc} />
<IdpTable idps={idps} orgId={params.orgId} />
</>

View File

@@ -23,9 +23,6 @@ import {
} from "@server/routers/remoteExitNode/types";
import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { build } from "@server/build";
import {
InfoSection,
InfoSectionContent,
@@ -36,6 +33,8 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon } from "lucide-react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function CredentialsPage() {
const { env } = useEnvContext();
@@ -45,6 +44,8 @@ export default function CredentialsPage() {
const t = useTranslations();
const { remoteExitNode } = useRemoteExitNodeContext();
const { isPaidUser } = usePaidStatus();
const [modalOpen, setModalOpen] = useState(false);
const [credentials, setCredentials] =
useState<PickRemoteExitNodeDefaultsResponse | null>(null);
@@ -57,16 +58,6 @@ export default function CredentialsPage() {
const [showCredentialsAlert, setShowCredentialsAlert] = useState(false);
const [shouldDisconnect, setShouldDisconnect] = useState(true);
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
const handleConfirmRegenerate = async () => {
try {
const response = await api.get<
@@ -138,7 +129,9 @@ export default function CredentialsPage() {
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<PaidFeaturesAlert />
<PaidFeaturesAlert
tiers={tierMatrix.rotateCredentials}
/>
<InfoSections cols={3}>
<InfoSection>
@@ -195,7 +188,7 @@ export default function CredentialsPage() {
</Alert>
)}
</SettingsSectionBody>
{build !== "oss" && (
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter>
<Button
variant="outline"
@@ -203,7 +196,9 @@ export default function CredentialsPage() {
setShouldDisconnect(false);
setModalOpen(true);
}}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(tierMatrix.rotateCredentials)
}
>
{t("regenerateCredentialsButton")}
</Button>
@@ -212,7 +207,9 @@ export default function CredentialsPage() {
setShouldDisconnect(true);
setModalOpen(true);
}}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(tierMatrix.rotateCredentials)
}
>
{t("remoteExitNodeRegenerateAndDisconnect")}
</Button>

View File

@@ -47,8 +47,8 @@ import { ListIdpsResponse } from "@server/routers/idp";
import { useTranslations } from "next-intl";
import { build } from "@server/build";
import Image from "next/image";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { TierId } from "@server/lib/billing/tiers";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
type UserType = "internal" | "oidc";
@@ -76,7 +76,7 @@ export default function Page() {
const api = createApiClient({ env });
const t = useTranslations();
const subscription = useSubscriptionStatusContext();
const { hasSaasSubscription } = usePaidStatus();
const [selectedOption, setSelectedOption] = useState<string | null>(
"internal"
@@ -238,7 +238,7 @@ export default function Page() {
}
async function fetchIdps() {
if (build === "saas" && !subscription?.subscribed) {
if (build === "saas" && !hasSaasSubscription(tierMatrix.orgOidc)) {
return;
}

View File

@@ -19,9 +19,6 @@ import { useTranslations } from "next-intl";
import { PickClientDefaultsResponse } from "@server/routers/client";
import { useClientContext } from "@app/hooks/useClientContext";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { build } from "@server/build";
import {
InfoSection,
InfoSectionContent,
@@ -33,6 +30,8 @@ import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon } from "lucide-react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { OlmInstallCommands } from "@app/components/olm-install-commands";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function CredentialsPage() {
const { env } = useEnvContext();
@@ -54,15 +53,7 @@ export default function CredentialsPage() {
const [showCredentialsAlert, setShowCredentialsAlert] = useState(false);
const [shouldDisconnect, setShouldDisconnect] = useState(true);
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
const { isPaidUser } = usePaidStatus();
const handleConfirmRegenerate = async () => {
try {
@@ -128,7 +119,9 @@ export default function CredentialsPage() {
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<PaidFeaturesAlert />
<PaidFeaturesAlert
tiers={tierMatrix.rotateCredentials}
/>
<InfoSections cols={3}>
<InfoSection>
@@ -181,7 +174,7 @@ export default function CredentialsPage() {
</Alert>
)}
</SettingsSectionBody>
{build !== "oss" && (
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter>
<Button
variant="outline"
@@ -189,7 +182,9 @@ export default function CredentialsPage() {
setShouldDisconnect(false);
setModalOpen(true);
}}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(tierMatrix.rotateCredentials)
}
>
{t("regenerateCredentialsButton")}
</Button>
@@ -198,7 +193,9 @@ export default function CredentialsPage() {
setShouldDisconnect(true);
setModalOpen(true);
}}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(tierMatrix.rotateCredentials)
}
>
{t("clientRegenerateAndDisconnect")}
</Button>

View File

@@ -28,10 +28,19 @@ import { createApiClient, formatAxiosError } from "@app/lib/api";
import { toast } from "@app/hooks/useToast";
import { useRouter } from "next/navigation";
import { useState, useEffect, useTransition } from "react";
import { Check, Ban, Shield, ShieldOff, Clock, CheckCircle2, XCircle } from "lucide-react";
import {
Check,
Ban,
Shield,
ShieldOff,
Clock,
CheckCircle2,
XCircle
} from "lucide-react";
import { useParams } from "next/navigation";
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
import { SiAndroid } from "react-icons/si";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
function formatTimestamp(timestamp: number | null | undefined): string {
if (!timestamp) return "-";
@@ -111,13 +120,13 @@ function getPlatformFieldConfig(
osVersion: { show: true, labelKey: "iosVersion" },
kernelVersion: { show: false, labelKey: "kernelVersion" },
arch: { show: true, labelKey: "architecture" },
deviceModel: { show: true, labelKey: "deviceModel" },
deviceModel: { show: true, labelKey: "deviceModel" }
},
android: {
osVersion: { show: true, labelKey: "androidVersion" },
kernelVersion: { show: true, labelKey: "kernelVersion" },
arch: { show: true, labelKey: "architecture" },
deviceModel: { show: true, labelKey: "deviceModel" },
deviceModel: { show: true, labelKey: "deviceModel" }
},
unknown: {
osVersion: { show: true, labelKey: "osVersion" },
@@ -133,7 +142,6 @@ function getPlatformFieldConfig(
return configs[normalizedPlatform] || configs.unknown;
}
export default function GeneralPage() {
const { client, updateClient } = useClientContext();
const { isPaidUser } = usePaidStatus();
@@ -145,11 +153,13 @@ export default function GeneralPage() {
const [approvalId, setApprovalId] = useState<number | null>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const [, startTransition] = useTransition();
const { env } = useEnvContext();
const showApprovalFeatures = build !== "oss" && isPaidUser;
const showApprovalFeatures =
build !== "oss" && isPaidUser(tierMatrix.deviceApprovals);
const formatPostureValue = (value: boolean | null | undefined) => {
if (value === null || value === undefined) return "-";
const formatPostureValue = (value: boolean | null | undefined | "-") => {
if (value === null || value === undefined || value === "-") return "-";
return (
<div className="flex items-center gap-2">
{value ? (
@@ -423,7 +433,8 @@ export default function GeneralPage() {
{t(
fieldConfig
.osVersion
?.labelKey || "osVersion"
?.labelKey ||
"osVersion"
)}
</InfoSectionTitle>
<InfoSectionContent>
@@ -559,8 +570,7 @@ export default function GeneralPage() {
</SettingsSection>
)}
{/* Device Security Section */}
{build !== "oss" && (
{!env.flags.disableEnterpriseFeatures && (
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
@@ -572,20 +582,27 @@ export default function GeneralPage() {
</SettingsSectionHeader>
<SettingsSectionBody>
{client.posture && Object.keys(client.posture).length > 0 ? (
<PaidFeaturesAlert tiers={tierMatrix.devicePosture} />
{client.posture &&
Object.keys(client.posture).length > 0 ? (
<>
{!isPaidUser && <PaidFeaturesAlert />}
<InfoSections cols={3}>
{client.posture.biometricsEnabled !== null &&
client.posture.biometricsEnabled !== undefined && (
{client.posture.biometricsEnabled !==
null &&
client.posture.biometricsEnabled !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("biometricsEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture.biometricsEnabled
client.posture
.biometricsEnabled
)
: "-"}
</InfoSectionContent>
@@ -593,15 +610,19 @@ export default function GeneralPage() {
)}
{client.posture.diskEncrypted !== null &&
client.posture.diskEncrypted !== undefined && (
client.posture.diskEncrypted !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("diskEncrypted")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture.diskEncrypted
client.posture
.diskEncrypted
)
: "-"}
</InfoSectionContent>
@@ -609,31 +630,40 @@ export default function GeneralPage() {
)}
{client.posture.firewallEnabled !== null &&
client.posture.firewallEnabled !== undefined && (
client.posture.firewallEnabled !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("firewallEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture.firewallEnabled
client.posture
.firewallEnabled
)
: "-"}
</InfoSectionContent>
</InfoSection>
)}
{client.posture.autoUpdatesEnabled !== null &&
client.posture.autoUpdatesEnabled !== undefined && (
{client.posture.autoUpdatesEnabled !==
null &&
client.posture.autoUpdatesEnabled !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("autoUpdatesEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture.autoUpdatesEnabled
client.posture
.autoUpdatesEnabled
)
: "-"}
</InfoSectionContent>
@@ -641,29 +671,40 @@ export default function GeneralPage() {
)}
{client.posture.tpmAvailable !== null &&
client.posture.tpmAvailable !== undefined && (
client.posture.tpmAvailable !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("tpmAvailable")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture.tpmAvailable
client.posture
.tpmAvailable
)
: "-"}
</InfoSectionContent>
</InfoSection>
)}
{client.posture.windowsAntivirusEnabled !== null &&
client.posture.windowsAntivirusEnabled !== undefined && (
{client.posture.windowsAntivirusEnabled !==
null &&
client.posture
.windowsAntivirusEnabled !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("windowsAntivirusEnabled")}
{t(
"windowsAntivirusEnabled"
)}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture
.windowsAntivirusEnabled
@@ -674,30 +715,40 @@ export default function GeneralPage() {
)}
{client.posture.macosSipEnabled !== null &&
client.posture.macosSipEnabled !== undefined && (
client.posture.macosSipEnabled !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("macosSipEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture.macosSipEnabled
client.posture
.macosSipEnabled
)
: "-"}
</InfoSectionContent>
</InfoSection>
)}
{client.posture.macosGatekeeperEnabled !== null &&
client.posture.macosGatekeeperEnabled !==
{client.posture.macosGatekeeperEnabled !==
null &&
client.posture
.macosGatekeeperEnabled !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("macosGatekeeperEnabled")}
{t(
"macosGatekeeperEnabled"
)}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture
.macosGatekeeperEnabled
@@ -707,15 +758,21 @@ export default function GeneralPage() {
</InfoSection>
)}
{client.posture.macosFirewallStealthMode !== null &&
client.posture.macosFirewallStealthMode !==
{client.posture.macosFirewallStealthMode !==
null &&
client.posture
.macosFirewallStealthMode !==
undefined && (
<InfoSection>
<InfoSectionTitle>
{t("macosFirewallStealthMode")}
{t(
"macosFirewallStealthMode"
)}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture
.macosFirewallStealthMode
@@ -725,7 +782,8 @@ export default function GeneralPage() {
</InfoSection>
)}
{client.posture.linuxAppArmorEnabled !== null &&
{client.posture.linuxAppArmorEnabled !==
null &&
client.posture.linuxAppArmorEnabled !==
undefined && (
<InfoSection>
@@ -733,7 +791,9 @@ export default function GeneralPage() {
{t("linuxAppArmorEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture
.linuxAppArmorEnabled
@@ -743,7 +803,8 @@ export default function GeneralPage() {
</InfoSection>
)}
{client.posture.linuxSELinuxEnabled !== null &&
{client.posture.linuxSELinuxEnabled !==
null &&
client.posture.linuxSELinuxEnabled !==
undefined && (
<InfoSection>
@@ -751,7 +812,9 @@ export default function GeneralPage() {
{t("linuxSELinuxEnabled")}
</InfoSectionTitle>
<InfoSectionContent>
{isPaidUser
{isPaidUser(
tierMatrix.devicePosture
)
? formatPostureValue(
client.posture
.linuxSELinuxEnabled

View File

@@ -1,5 +1,5 @@
import AuthPageBrandingForm from "@app/components/AuthPageBrandingForm";
import AuthPageSettings from "@app/components/private/AuthPageSettings";
import AuthPageSettings from "@app/components/AuthPageSettings";
import { SettingsContainer } from "@app/components/Settings";
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
@@ -20,11 +20,6 @@ export interface AuthPageProps {
export default async function AuthPage(props: AuthPageProps) {
const orgId = (await props.params).orgId;
// custom auth branding is only available in enterprise and saas
if (build === "oss") {
redirect(`/${orgId}/settings/general/`);
}
let subscriptionStatus: GetOrgTierResponse | null = null;
try {
const subRes = await getCachedSubscription(orgId);

View File

@@ -10,6 +10,7 @@ import { getTranslations } from "next-intl/server";
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
import { build } from "@server/build";
import { pullEnv } from "@app/lib/pullEnv";
type GeneralSettingsProps = {
children: React.ReactNode;
@@ -23,6 +24,7 @@ export default async function GeneralSettingsPage({
const { orgId } = await params;
const user = await verifySession();
const env = pullEnv();
if (!user) {
redirect(`/`);
@@ -55,14 +57,17 @@ export default async function GeneralSettingsPage({
{
title: t("security"),
href: `/{orgId}/settings/general/security`
}
},
// PaidFeaturesAlert
...(!env.flags.disableEnterpriseFeatures
? [
{
title: t("authPage"),
href: `/{orgId}/settings/general/auth-page`
}
]
: [])
];
if (build !== "oss") {
navItems.push({
title: t("authPage"),
href: `/{orgId}/settings/general/auth-page`
});
}
return (
<>

View File

@@ -3,12 +3,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { Button } from "@app/components/ui/button";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { toast } from "@app/hooks/useToast";
import {
useState,
useRef,
useActionState,
type ComponentRef
} from "react";
import { useState, useRef, useActionState, type ComponentRef } from "react";
import {
Form,
FormControl,
@@ -48,6 +43,7 @@ import { SwitchInput } from "@app/components/SwitchInput";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import type { OrgContextType } from "@app/contexts/orgContext";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
// Session length options in hours
const SESSION_LENGTH_OPTIONS = [
@@ -107,10 +103,13 @@ type SectionFormProps = {
export default function SecurityPage() {
const { org } = useOrgContext();
const { env } = useEnvContext();
return (
<SettingsContainer>
<LogRetentionSectionForm org={org.org} />
{build !== "oss" && <SecuritySettingsSectionForm org={org.org} />}
{!env.flags.disableEnterpriseFeatures && (
<SecuritySettingsSectionForm org={org.org} />
)}
</SettingsContainer>
);
}
@@ -137,10 +136,11 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
const router = useRouter();
const t = useTranslations();
const { isPaidUser, hasSaasSubscription } = usePaidStatus();
const { isPaidUser, subscriptionTier } = usePaidStatus();
const [, formAction, loadingSave] = useActionState(performSave, null);
const api = createApiClient(useEnvContext());
const { env } = useEnvContext();
const api = createApiClient({ env });
async function performSave() {
const isValid = await form.trigger();
@@ -218,13 +218,35 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
<SelectContent>
{LOG_RETENTION_OPTIONS.filter(
(option) => {
if (
hasSaasSubscription &&
option.value >
30
) {
if (build != "saas") {
return true;
}
let maxDays: number;
if (!subscriptionTier) {
// No tier
maxDays = 3;
} else if (subscriptionTier == "enterprise") {
// Enterprise - no limit
return true;
} else if (subscriptionTier == "tier3") {
maxDays = 90;
} else if (subscriptionTier == "tier2") {
maxDays = 30;
} else if (subscriptionTier == "tier1") {
maxDays = 7;
} else {
// Default to most restrictive
maxDays = 3;
}
// Filter out options that exceed the max
// Special values: -1 (forever) and 9001 (end of year) should be filtered
if (option.value < 0 || option.value > maxDays) {
return false;
}
return true;
}
).map((option) => (
@@ -243,15 +265,19 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
)}
/>
{build !== "oss" && (
{!env.flags.disableEnterpriseFeatures && (
<>
<PaidFeaturesAlert />
<PaidFeaturesAlert
tiers={tierMatrix.accessLogs}
/>
<FormField
control={form.control}
name="settingsLogRetentionDaysAccess"
render={({ field }) => {
const isDisabled = !isPaidUser;
const isDisabled = !isPaidUser(
tierMatrix.accessLogs
);
return (
<FormItem>
@@ -289,7 +315,40 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
/>
</SelectTrigger>
<SelectContent>
{LOG_RETENTION_OPTIONS.map(
{LOG_RETENTION_OPTIONS.filter(
(option) => {
if (build != "saas") {
return true;
}
let maxDays: number;
if (!subscriptionTier) {
// No tier
maxDays = 3;
} else if (subscriptionTier == "enterprise") {
// Enterprise - no limit
return true;
} else if (subscriptionTier == "tier3") {
maxDays = 90;
} else if (subscriptionTier == "tier2") {
maxDays = 30;
} else if (subscriptionTier == "tier1") {
maxDays = 7;
} else {
// Default to most restrictive
maxDays = 3;
}
// Filter out options that exceed the max
// Special values: -1 (forever) and 9001 (end of year) should be filtered
if (option.value < 0 || option.value > maxDays) {
return false;
}
return true;
}
).map(
(
option
) => (
@@ -317,7 +376,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
control={form.control}
name="settingsLogRetentionDaysAction"
render={({ field }) => {
const isDisabled = !isPaidUser;
const isDisabled = !isPaidUser(
tierMatrix.actionLogs
);
return (
<FormItem>
@@ -355,7 +416,40 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
/>
</SelectTrigger>
<SelectContent>
{LOG_RETENTION_OPTIONS.map(
{LOG_RETENTION_OPTIONS.filter(
(option) => {
if (build != "saas") {
return true;
}
let maxDays: number;
if (!subscriptionTier) {
// No tier
maxDays = 3;
} else if (subscriptionTier == "enterprise") {
// Enterprise - no limit
return true;
} else if (subscriptionTier == "tier3") {
maxDays = 90;
} else if (subscriptionTier == "tier2") {
maxDays = 30;
} else if (subscriptionTier == "tier1") {
maxDays = 7;
} else {
// Default to most restrictive
maxDays = 3;
}
// Filter out options that exceed the max
// Special values: -1 (forever) and 9001 (end of year) should be filtered
if (option.value < 0 || option.value > maxDays) {
return false;
}
return true;
}
).map(
(
option
) => (
@@ -522,12 +616,17 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) {
id="security-settings-section-form"
className="space-y-4"
>
<PaidFeaturesAlert />
<PaidFeaturesAlert
tiers={tierMatrix.twoFactorEnforcement}
/>
<FormField
control={form.control}
name="requireTwoFactor"
render={({ field }) => {
const isDisabled = !isPaidUser;
const isDisabled = !isPaidUser(
tierMatrix.twoFactorEnforcement
);
return (
<FormItem className="col-span-2">
@@ -574,7 +673,9 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) {
control={form.control}
name="maxSessionLengthHours"
render={({ field }) => {
const isDisabled = !isPaidUser;
const isDisabled = !isPaidUser(
tierMatrix.sessionDurationPolicies
);
return (
<FormItem className="col-span-2">
@@ -654,7 +755,9 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) {
control={form.control}
name="passwordExpiryDays"
render={({ field }) => {
const isDisabled = !isPaidUser;
const isDisabled = !isPaidUser(
tierMatrix.passwordExpirationPolicies
);
return (
<FormItem className="col-span-2">
@@ -740,7 +843,12 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) {
type="submit"
form="security-settings-section-form"
loading={loadingSave}
disabled={loadingSave}
disabled={
loadingSave ||
!isPaidUser(tierMatrix.twoFactorEnforcement) ||
!isPaidUser(tierMatrix.sessionDurationPolicies) ||
!isPaidUser(tierMatrix.passwordExpirationPolicies)
}
>
{t("saveSettings")}
</Button>

View File

@@ -13,13 +13,13 @@ import { ArrowUpRight, Key, User } from "lucide-react";
import Link from "next/link";
import { ColumnFilter } from "@app/components/ColumnFilter";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { build } from "@server/build";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
import axios from "axios";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function GeneralPage() {
const router = useRouter();
@@ -27,8 +27,8 @@ export default function GeneralPage() {
const api = createApiClient(useEnvContext());
const t = useTranslations();
const { orgId } = useParams();
const subscription = useSubscriptionStatusContext();
const { isUnlocked } = useLicenseStatusContext();
const { isPaidUser } = usePaidStatus();
const [rows, setRows] = useState<any[]>([]);
const [isRefreshing, setIsRefreshing] = useState(false);
@@ -207,10 +207,7 @@ export default function GeneralPage() {
}
) => {
console.log("Date range changed:", { startDate, endDate, page, size });
if (
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked())
) {
if (!isPaidUser(tierMatrix.accessLogs) || build === "oss") {
console.log(
"Access denied: subscription inactive or license locked"
);
@@ -611,21 +608,7 @@ export default function GeneralPage() {
description={t("accessLogsDescription")}
/>
{build == "saas" && !subscription?.subscribed ? (
<Alert variant="info" className="mb-6">
<AlertDescription>
{t("subscriptionRequiredToUse")}
</AlertDescription>
</Alert>
) : null}
{build == "enterprise" && !isUnlocked() ? (
<Alert variant="info" className="mb-6">
<AlertDescription>
{t("licenseRequiredToUse")}
</AlertDescription>
</Alert>
) : null}
<PaidFeaturesAlert tiers={tierMatrix.accessLogs} />
<LogDataTable
columns={columns}
@@ -635,6 +618,9 @@ export default function GeneralPage() {
isRefreshing={isRefreshing}
onExport={() => startTransition(exportData)}
isExporting={isExporting}
// isExportDisabled={ // not disabling this because the user should be able to click the button and get the feedback about needing to upgrade the plan
// !isPaidUser(tierMatrix.accessLogs) || build === "oss"
// }
onDateRangeChange={handleDateRangeChange}
dateRange={{
start: dateRange.startDate,
@@ -654,10 +640,7 @@ export default function GeneralPage() {
// Row expansion props
expandable={true}
renderExpandedRow={renderExpandedRow}
disabled={
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked())
}
disabled={!isPaidUser(tierMatrix.accessLogs) || build === "oss"}
/>
</>
);

View File

@@ -2,16 +2,16 @@
import { ColumnFilter } from "@app/components/ColumnFilter";
import { DateTimeValue } from "@app/components/DateTimePicker";
import { LogDataTable } from "@app/components/LogDataTable";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api";
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { ColumnDef } from "@tanstack/react-table";
import axios from "axios";
import { Key, User } from "lucide-react";
@@ -25,8 +25,8 @@ export default function GeneralPage() {
const t = useTranslations();
const { orgId } = useParams();
const searchParams = useSearchParams();
const subscription = useSubscriptionStatusContext();
const { isUnlocked } = useLicenseStatusContext();
const { isPaidUser } = usePaidStatus();
const [rows, setRows] = useState<any[]>([]);
const [isRefreshing, setIsRefreshing] = useState(false);
@@ -92,6 +92,9 @@ export default function GeneralPage() {
// Trigger search with default values on component mount
useEffect(() => {
if (build === "oss") {
return;
}
const defaultRange = getDefaultDateRange();
queryDateTime(
defaultRange.startDate,
@@ -191,10 +194,7 @@ export default function GeneralPage() {
}
) => {
console.log("Date range changed:", { startDate, endDate, page, size });
if (
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked())
) {
if (!isPaidUser(tierMatrix.actionLogs)) {
console.log(
"Access denied: subscription inactive or license locked"
);
@@ -461,21 +461,7 @@ export default function GeneralPage() {
description={t("actionLogsDescription")}
/>
{build == "saas" && !subscription?.subscribed ? (
<Alert variant="info" className="mb-6">
<AlertDescription>
{t("subscriptionRequiredToUse")}
</AlertDescription>
</Alert>
) : null}
{build == "enterprise" && !isUnlocked() ? (
<Alert variant="info" className="mb-6">
<AlertDescription>
{t("licenseRequiredToUse")}
</AlertDescription>
</Alert>
) : null}
<PaidFeaturesAlert tiers={tierMatrix.actionLogs} />
<LogDataTable
columns={columns}
@@ -486,6 +472,9 @@ export default function GeneralPage() {
onRefresh={refreshData}
isRefreshing={isRefreshing}
onExport={() => startTransition(exportData)}
// isExportDisabled={ // not disabling this because the user should be able to click the button and get the feedback about needing to upgrade the plan
// !isPaidUser(tierMatrix.logExport) || build === "oss"
// }
isExporting={isExporting}
onDateRangeChange={handleDateRangeChange}
dateRange={{
@@ -506,10 +495,7 @@ export default function GeneralPage() {
// Row expansion props
expandable={true}
renderExpandedRow={renderExpandedRow}
disabled={
(build == "saas" && !subscription?.subscribed) ||
(build == "enterprise" && !isUnlocked())
}
disabled={!isPaidUser(tierMatrix.actionLogs) || build === "oss"}
/>
</>
);

View File

@@ -1,54 +1,3 @@
"use client";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import AuthPageSettings, {
AuthPageSettingsRef
} from "@app/components/private/AuthPageSettings";
import { Button } from "@app/components/ui/button";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { userOrgUserContext } from "@app/hooks/useOrgUserContext";
import { toast } from "@app/hooks/useToast";
import { useState, useRef } from "react";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { formatAxiosError } from "@app/lib/api";
import { AxiosResponse } from "axios";
import { DeleteOrgResponse, ListUserOrgsResponse } from "@server/routers/org";
import { useRouter } from "next/navigation";
import {
SettingsContainer,
SettingsSection,
SettingsSectionHeader,
SettingsSectionTitle,
SettingsSectionDescription,
SettingsSectionBody,
SettingsSectionForm,
SettingsSectionFooter
} from "@app/components/Settings";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from "next-intl";
import { build } from "@server/build";
export default function GeneralPage() {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const router = useRouter();
const api = createApiClient(useEnvContext());
const t = useTranslations();
const { env } = useEnvContext();
return <p>dfas</p>;
return null;
}

View File

@@ -16,6 +16,7 @@ import Link from "next/link";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState, useTransition } from "react";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { build } from "@server/build";
export default function GeneralPage() {
const router = useRouter();
@@ -110,6 +111,9 @@ export default function GeneralPage() {
// Trigger search with default values on component mount
useEffect(() => {
if (build === "oss") {
return;
}
const defaultRange = getDefaultDateRange();
queryDateTime(
defaultRange.startDate,

View File

@@ -44,6 +44,7 @@ import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { orgQueries, resourceQueries } from "@app/lib/queries";
import { zodResolver } from "@hookform/resolvers/zod";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { UserType } from "@server/types/UserTypes";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import SetResourcePasswordForm from "components/SetResourcePasswordForm";
@@ -131,7 +132,7 @@ export default function ResourceAuthenticationPage() {
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
orgQueries.identityProviders({
orgId: org.org.orgId,
useOrgOnlyIdp: env.flags.useOrgOnlyIdp
useOrgOnlyIdp: env.app.identityProviderMode === "org"
})
);
@@ -164,7 +165,7 @@ export default function ResourceAuthenticationPage() {
const allIdps = useMemo(() => {
if (build === "saas") {
if (isPaidUser) {
if (isPaidUser(tierMatrix.orgOidc)) {
return orgIdps.map((idp) => ({
id: idp.idpId,
text: idp.name

View File

@@ -50,9 +50,6 @@ import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import z from "zod";
import { build } from "@server/build";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
import {
@@ -63,6 +60,8 @@ import {
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { GetResourceResponse } from "@server/routers/resource/getResource";
import type { ResourceContextType } from "@app/contexts/resourceContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
type MaintenanceSectionFormProps = {
resource: GetResourceResponse;
@@ -76,8 +75,7 @@ function MaintenanceSectionForm({
const { env } = useEnvContext();
const t = useTranslations();
const api = createApiClient({ env });
const { isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const { isPaidUser } = usePaidStatus();
const MaintenanceFormSchema = z.object({
maintenanceModeEnabled: z.boolean().optional(),
@@ -157,13 +155,6 @@ function MaintenanceSectionForm({
}
}
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
if (!resource.http) {
return null;
}
@@ -187,13 +178,16 @@ function MaintenanceSectionForm({
className="space-y-4"
id="maintenance-settings-form"
>
<PaidFeaturesAlert></PaidFeaturesAlert>
<PaidFeaturesAlert
tiers={tierMatrix.maintencePage}
/>
<FormField
control={maintenanceForm.control}
name="maintenanceModeEnabled"
render={({ field }) => {
const isDisabled =
isSecurityFeatureDisabled() || resource.http === false;
!isPaidUser(tierMatrix.maintencePage) ||
resource.http === false;
return (
<FormItem>
@@ -259,7 +253,11 @@ function MaintenanceSectionForm({
defaultValue={
field.value
}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-start space-x-3 space-y-0">
@@ -332,7 +330,11 @@ function MaintenanceSectionForm({
<FormControl>
<Input
{...field}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
placeholder="We'll be back soon!"
/>
</FormControl>
@@ -358,7 +360,11 @@ function MaintenanceSectionForm({
<Textarea
{...field}
rows={4}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
placeholder={t(
"maintenancePageMessagePlaceholder"
)}
@@ -387,7 +393,11 @@ function MaintenanceSectionForm({
<FormControl>
<Input
{...field}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
placeholder={t(
"maintenanceTime"
)}
@@ -413,7 +423,10 @@ function MaintenanceSectionForm({
<Button
type="submit"
loading={maintenanceSaveLoading}
disabled={maintenanceSaveLoading}
disabled={
maintenanceSaveLoading ||
!isPaidUser(tierMatrix.maintencePage)
}
form="maintenance-settings-form"
>
{t("saveSettings")}
@@ -739,7 +752,7 @@ export default function GeneralForm() {
</SettingsSectionFooter>
</SettingsSection>
{build !== "oss" && (
{!env.flags.disableEnterpriseFeatures && (
<MaintenanceSectionForm
resource={resource}
updateResource={updateResource}

View File

@@ -20,9 +20,6 @@ import { PickSiteDefaultsResponse } from "@server/routers/site";
import { useSiteContext } from "@app/hooks/useSiteContext";
import { generateKeypair } from "../wireguardConfig";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { build } from "@server/build";
import {
InfoSection,
InfoSectionContent,
@@ -40,6 +37,8 @@ import {
import { QRCodeCanvas } from "qrcode.react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { NewtSiteInstallCommands } from "@app/components/newt-install-commands";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
export default function CredentialsPage() {
const { env } = useEnvContext();
@@ -65,15 +64,7 @@ export default function CredentialsPage() {
const [loadingDefaults, setLoadingDefaults] = useState(false);
const [shouldDisconnect, setShouldDisconnect] = useState(true);
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
const { isPaidUser } = usePaidStatus();
// Fetch site defaults for wireguard sites to show in obfuscated config
useEffect(() => {
@@ -205,7 +196,9 @@ export default function CredentialsPage() {
</SettingsSectionDescription>
</SettingsSectionHeader>
<PaidFeaturesAlert />
<PaidFeaturesAlert
tiers={tierMatrix.rotateCredentials}
/>
<SettingsSectionBody>
<InfoSections cols={3}>
@@ -269,7 +262,7 @@ export default function CredentialsPage() {
</Alert>
)}
</SettingsSectionBody>
{build !== "oss" && (
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter>
<Button
variant="outline"
@@ -277,7 +270,11 @@ export default function CredentialsPage() {
setShouldDisconnect(false);
setModalOpen(true);
}}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.rotateCredentials
)
}
>
{t("regenerateCredentialsButton")}
</Button>
@@ -286,7 +283,11 @@ export default function CredentialsPage() {
setShouldDisconnect(true);
setModalOpen(true);
}}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.rotateCredentials
)
}
>
{t("siteRegenerateAndDisconnect")}
</Button>
@@ -313,7 +314,9 @@ export default function CredentialsPage() {
</SettingsSectionDescription>
</SettingsSectionHeader>
<PaidFeaturesAlert />
<PaidFeaturesAlert
tiers={tierMatrix.rotateCredentials}
/>
<SettingsSectionBody>
{!loadingDefaults && (
@@ -383,11 +386,15 @@ export default function CredentialsPage() {
</>
)}
</SettingsSectionBody>
{build === "enterprise" && (
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter>
<Button
onClick={() => setModalOpen(true)}
disabled={isSecurityFeatureDisabled()}
disabled={
!isPaidUser(
tierMatrix.rotateCredentials
)
}
>
{t("siteRegenerateAndDisconnect")}
</Button>

View File

@@ -50,7 +50,7 @@ export default async function UsersPage(props: PageProps) {
title={t("userTitle")}
description={t("userDescription")}
/>
<Alert variant="neutral" className="mb-6">
<Alert className="mb-6">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t("userAbount")}

View File

@@ -76,12 +76,13 @@ export default async function Page(props: {
// Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled)
const useSmartLogin =
build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp);
build === "saas" ||
(build === "enterprise" && env.app.identityProviderMode === "org");
let loginIdps: LoginFormIDP[] = [];
if (!useSmartLogin) {
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
if (build === "oss" || !env.flags.useOrgOnlyIdp) {
if (build === "oss" || env.app.identityProviderMode !== "org") {
const idpsRes = await cache(
async () =>
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
@@ -165,7 +166,8 @@ export default async function Page(props: {
forceLogin={forceLogin}
showOrgLogin={
!isInvite &&
(build === "saas" || env.flags.useOrgOnlyIdp)
(build === "saas" ||
env.app.identityProviderMode === "org")
}
searchParams={searchParams}
defaultUser={defaultUser}
@@ -188,7 +190,8 @@ export default async function Page(props: {
</p>
)}
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
{!isInvite &&
(build === "saas" || env.app.identityProviderMode === "org") ? (
<OrgSignInLink
href={`/auth/org${buildQueryString(searchParams)}`}
linkText={t("orgAuthSignInToOrg")}

View File

@@ -24,7 +24,7 @@ export default async function OrgAuthPage(props: {
const env = pullEnv();
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
if (build !== "saas" && env.app.identityProviderMode !== "org") {
const queryString = new URLSearchParams(searchParams as any).toString();
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
}

View File

@@ -14,7 +14,7 @@ import {
LoadLoginPageResponse
} from "@server/routers/loginPage/types";
import { GetSessionTransferTokenRenponse } from "@server/routers/auth/types";
import ValidateSessionTransferToken from "@app/components/private/ValidateSessionTransferToken";
import ValidateSessionTransferToken from "@app/components/ValidateSessionTransferToken";
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
import { OrgSelectionForm } from "@app/components/OrgSelectionForm";
import OrgLoginPage from "@app/components/OrgLoginPage";
@@ -35,7 +35,7 @@ export default async function OrgAuthPage(props: {
const env = pullEnv();
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
if (build !== "saas" && env.app.identityProviderMode !== "org") {
redirect("/");
}

View File

@@ -23,8 +23,6 @@ import type {
LoadLoginPageBrandingResponse,
LoadLoginPageResponse
} from "@server/routers/loginPage/types";
import { GetOrgTierResponse } from "@server/routers/billing/types";
import { TierId } from "@server/lib/billing/tiers";
import { CheckOrgUserAccessResponse } from "@server/routers/org";
import OrgPolicyRequired from "@app/components/OrgPolicyRequired";
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
@@ -204,7 +202,7 @@ export default async function ResourceAuthPage(props: {
}
let loginIdps: LoginFormIDP[] = [];
if (build === "saas" || env.flags.useOrgOnlyIdp) {
if (build === "saas" || env.app.identityProviderMode === "org") {
if (subscribed) {
const idpsRes = await cache(
async () =>

View File

@@ -5,7 +5,7 @@ import { ThemeProvider } from "@app/providers/ThemeProvider";
import EnvProvider from "@app/providers/EnvProvider";
import { pullEnv } from "@app/lib/pullEnv";
import ThemeDataProvider from "@app/providers/ThemeDataProvider";
import SplashImage from "@app/components/private/SplashImage";
import SplashImage from "@app/components/SplashImage";
import SupportStatusProvider from "@app/providers/SupporterStatusProvider";
import { priv } from "@app/lib/api";
import { AxiosResponse } from "axios";

View File

@@ -121,7 +121,11 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
href: "/{orgId}/settings/access/roles",
icon: <Users className="size-4 flex-none" />
},
...(build === "saas" || env?.flags.useOrgOnlyIdp
// PaidFeaturesAlert
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
build === "saas" ||
env?.app.identityProviderMode === "org" ||
(env?.app.identityProviderMode === undefined && build !== "oss")
? [
{
title: "sidebarIdentityProviders",
@@ -130,7 +134,7 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
}
]
: []),
...(build !== "oss"
...(!env?.flags.disableEnterpriseFeatures
? [
{
title: "sidebarApprovals",
@@ -155,7 +159,7 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
href: "/{orgId}/settings/logs/request",
icon: <SquareMousePointer className="size-4 flex-none" />
},
...(build != "oss"
...(!env?.flags.disableEnterpriseFeatures
? [
{
title: "sidebarLogsAccess",
@@ -248,7 +252,9 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
href: "/admin/api-keys",
icon: <KeyRound className="size-4 flex-none" />
},
...(build === "oss" || !env?.flags.useOrgOnlyIdp
...(build === "oss" ||
env?.app.identityProviderMode === "global" ||
env?.app.identityProviderMode === undefined
? [
{
title: "sidebarIdentityProviders",