diff --git a/messages/en-US.json b/messages/en-US.json index e9d8cc37..07025a58 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2247,6 +2247,7 @@ "actionLogsDescription": "View a history of actions performed in this organization", "accessLogsDescription": "View access auth requests for resources in this organization", "licenseRequiredToUse": "An Enterprise license is required to use this feature.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature.", "certResolver": "Certificate Resolver", "certResolverDescription": "Select the certificate resolver to use for this resource.", "selectCertResolver": "Select Certificate Resolver", diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index 66a6432f..12901d0c 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -56,19 +56,29 @@ async function query(clientId?: number, niceId?: string, orgId?: string) { } type PostureData = { - biometricsEnabled?: boolean | null; - diskEncrypted?: boolean | null; - firewallEnabled?: boolean | null; - autoUpdatesEnabled?: boolean | null; - tpmAvailable?: boolean | null; - windowsAntivirusEnabled?: boolean | null; - macosSipEnabled?: boolean | null; - macosGatekeeperEnabled?: boolean | null; - macosFirewallStealthMode?: boolean | null; - linuxAppArmorEnabled?: boolean | null; - linuxSELinuxEnabled?: boolean | null; + biometricsEnabled?: boolean | null | "-"; + diskEncrypted?: boolean | null | "-"; + firewallEnabled?: boolean | null | "-"; + autoUpdatesEnabled?: boolean | null | "-"; + tpmAvailable?: boolean | null | "-"; + windowsAntivirusEnabled?: boolean | null | "-"; + macosSipEnabled?: boolean | null | "-"; + macosGatekeeperEnabled?: boolean | null | "-"; + macosFirewallStealthMode?: boolean | null | "-"; + linuxAppArmorEnabled?: boolean | null | "-"; + linuxSELinuxEnabled?: boolean | null | "-"; }; +function maskPostureDataWithPlaceholder(posture: PostureData): PostureData { + const masked: PostureData = {}; + for (const key of Object.keys(posture) as (keyof PostureData)[]) { + if (posture[key] !== undefined && posture[key] !== null) { + (masked as Record)[key] = "-"; + } + } + return masked; +} + function getPlatformPostureData( platform: string | null | undefined, fingerprint: typeof currentFingerprint.$inferSelect | null @@ -294,32 +304,34 @@ export async function getClient( // Build fingerprint data if available const fingerprintData = client.currentFingerprint ? { - username: client.currentFingerprint.username || null, - hostname: client.currentFingerprint.hostname || null, - platform: client.currentFingerprint.platform || null, - osVersion: client.currentFingerprint.osVersion || null, - kernelVersion: - client.currentFingerprint.kernelVersion || null, - arch: client.currentFingerprint.arch || null, - deviceModel: client.currentFingerprint.deviceModel || null, - serialNumber: client.currentFingerprint.serialNumber || null, - firstSeen: client.currentFingerprint.firstSeen || null, - lastSeen: client.currentFingerprint.lastSeen || null - } + username: client.currentFingerprint.username || null, + hostname: client.currentFingerprint.hostname || null, + platform: client.currentFingerprint.platform || null, + osVersion: client.currentFingerprint.osVersion || null, + kernelVersion: + client.currentFingerprint.kernelVersion || null, + arch: client.currentFingerprint.arch || null, + deviceModel: client.currentFingerprint.deviceModel || null, + serialNumber: client.currentFingerprint.serialNumber || null, + firstSeen: client.currentFingerprint.firstSeen || null, + lastSeen: client.currentFingerprint.lastSeen || null + } : null; // Build posture data if available (platform-specific) - // Only return posture data if org is licensed/subscribed - let postureData: PostureData | null = null; + // Licensed: real values; not licensed: same keys but values set to "-" + const rawPosture = getPlatformPostureData( + client.currentFingerprint?.platform || null, + client.currentFingerprint + ); const isOrgLicensed = await isLicensedOrSubscribed( client.clients.orgId ); - if (isOrgLicensed) { - postureData = getPlatformPostureData( - client.currentFingerprint?.platform || null, - client.currentFingerprint - ); - } + const postureData: PostureData | null = rawPosture + ? isOrgLicensed + ? rawPosture + : maskPostureDataWithPlaceholder(rawPosture) + : null; const data: GetClientResponse = { ...client.clients, diff --git a/src/app/[orgId]/settings/(private)/idp/create/page.tsx b/src/app/[orgId]/settings/(private)/idp/create/page.tsx index 5ae4f237..2f248077 100644 --- a/src/app/[orgId]/settings/(private)/idp/create/page.tsx +++ b/src/app/[orgId]/settings/(private)/idp/create/page.tsx @@ -27,6 +27,7 @@ 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"; @@ -51,6 +52,7 @@ export default function Page() { >("role"); const { isUnlocked } = useLicenseStatusContext(); const t = useTranslations(); + const { isPaidUser } = usePaidStatus(); const params = useParams(); @@ -806,7 +808,7 @@ export default function Page() { - - - )} + + + + diff --git a/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx index 9c4bf2bf..e6b5ff20 100644 --- a/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx @@ -61,7 +61,9 @@ export default function CredentialsPage() { const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked(); const isSaasNotSubscribed = build === "saas" && !subscription?.isSubscribed(); - return isEnterpriseNotLicensed || isSaasNotSubscribed; + return ( + isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss" + ); }; const handleConfirmRegenerate = async () => { @@ -181,29 +183,27 @@ export default function CredentialsPage() { )} - {build !== "oss" && ( - - - - - )} + + + + @@ -559,217 +567,231 @@ export default function GeneralPage() { )} - {/* Device Security Section */} - {build !== "oss" && ( - - - - {t("deviceSecurity")} - - - {t("deviceSecurityDescription")} - - + + + + {t("deviceSecurity")} + + + {t("deviceSecurityDescription")} + + - - {client.posture && Object.keys(client.posture).length > 0 ? ( - <> - {!isPaidUser && } - - {client.posture.biometricsEnabled !== null && - client.posture.biometricsEnabled !== undefined && ( - - - {t("biometricsEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture.biometricsEnabled - ) - : "-"} - - - )} + + + {client.posture && + Object.keys(client.posture).length > 0 ? ( + <> + + {client.posture.biometricsEnabled !== null && + client.posture.biometricsEnabled !== + undefined && ( + + + {t("biometricsEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .biometricsEnabled + ) + : "-"} + + + )} - {client.posture.diskEncrypted !== null && - client.posture.diskEncrypted !== undefined && ( - - - {t("diskEncrypted")} - - - {isPaidUser - ? formatPostureValue( - client.posture.diskEncrypted - ) - : "-"} - - - )} + {client.posture.diskEncrypted !== null && + client.posture.diskEncrypted !== + undefined && ( + + + {t("diskEncrypted")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .diskEncrypted + ) + : "-"} + + + )} - {client.posture.firewallEnabled !== null && - client.posture.firewallEnabled !== undefined && ( - - - {t("firewallEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture.firewallEnabled - ) - : "-"} - - - )} + {client.posture.firewallEnabled !== null && + client.posture.firewallEnabled !== + undefined && ( + + + {t("firewallEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .firewallEnabled + ) + : "-"} + + + )} - {client.posture.autoUpdatesEnabled !== null && - client.posture.autoUpdatesEnabled !== undefined && ( - - - {t("autoUpdatesEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture.autoUpdatesEnabled - ) - : "-"} - - - )} + {client.posture.autoUpdatesEnabled !== null && + client.posture.autoUpdatesEnabled !== + undefined && ( + + + {t("autoUpdatesEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .autoUpdatesEnabled + ) + : "-"} + + + )} - {client.posture.tpmAvailable !== null && - client.posture.tpmAvailable !== undefined && ( - - - {t("tpmAvailable")} - - - {isPaidUser - ? formatPostureValue( - client.posture.tpmAvailable - ) - : "-"} - - - )} + {client.posture.tpmAvailable !== null && + client.posture.tpmAvailable !== + undefined && ( + + + {t("tpmAvailable")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .tpmAvailable + ) + : "-"} + + + )} - {client.posture.windowsAntivirusEnabled !== null && - client.posture.windowsAntivirusEnabled !== undefined && ( - - - {t("windowsAntivirusEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture - .windowsAntivirusEnabled - ) - : "-"} - - - )} + {client.posture.windowsAntivirusEnabled !== + null && + client.posture.windowsAntivirusEnabled !== + undefined && ( + + + {t("windowsAntivirusEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .windowsAntivirusEnabled + ) + : "-"} + + + )} - {client.posture.macosSipEnabled !== null && - client.posture.macosSipEnabled !== undefined && ( - - - {t("macosSipEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture.macosSipEnabled - ) - : "-"} - - - )} + {client.posture.macosSipEnabled !== null && + client.posture.macosSipEnabled !== + undefined && ( + + + {t("macosSipEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .macosSipEnabled + ) + : "-"} + + + )} - {client.posture.macosGatekeeperEnabled !== null && - client.posture.macosGatekeeperEnabled !== - undefined && ( - - - {t("macosGatekeeperEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture - .macosGatekeeperEnabled - ) - : "-"} - - - )} + {client.posture.macosGatekeeperEnabled !== + null && + client.posture.macosGatekeeperEnabled !== + undefined && ( + + + {t("macosGatekeeperEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .macosGatekeeperEnabled + ) + : "-"} + + + )} - {client.posture.macosFirewallStealthMode !== null && - client.posture.macosFirewallStealthMode !== - undefined && ( - - - {t("macosFirewallStealthMode")} - - - {isPaidUser - ? formatPostureValue( - client.posture - .macosFirewallStealthMode - ) - : "-"} - - - )} + {client.posture.macosFirewallStealthMode !== + null && + client.posture.macosFirewallStealthMode !== + undefined && ( + + + {t("macosFirewallStealthMode")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .macosFirewallStealthMode + ) + : "-"} + + + )} - {client.posture.linuxAppArmorEnabled !== null && - client.posture.linuxAppArmorEnabled !== - undefined && ( - - - {t("linuxAppArmorEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture - .linuxAppArmorEnabled - ) - : "-"} - - - )} + {client.posture.linuxAppArmorEnabled !== null && + client.posture.linuxAppArmorEnabled !== + undefined && ( + + + {t("linuxAppArmorEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .linuxAppArmorEnabled + ) + : "-"} + + + )} - {client.posture.linuxSELinuxEnabled !== null && - client.posture.linuxSELinuxEnabled !== - undefined && ( - - - {t("linuxSELinuxEnabled")} - - - {isPaidUser - ? formatPostureValue( - client.posture - .linuxSELinuxEnabled - ) - : "-"} - - - )} - - - ) : ( -
- {t("noData")} -
- )} -
-
- )} + {client.posture.linuxSELinuxEnabled !== null && + client.posture.linuxSELinuxEnabled !== + undefined && ( + + + {t("linuxSELinuxEnabled")} + + + {isPaidUser + ? formatPostureValue( + client.posture + .linuxSELinuxEnabled + ) + : "-"} + + + )} + + + ) : ( +
+ {t("noData")} +
+ )} + +
); } diff --git a/src/app/[orgId]/settings/general/auth-page/page.tsx b/src/app/[orgId]/settings/general/auth-page/page.tsx index 73c54827..9ffa8e04 100644 --- a/src/app/[orgId]/settings/general/auth-page/page.tsx +++ b/src/app/[orgId]/settings/general/auth-page/page.tsx @@ -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); diff --git a/src/app/[orgId]/settings/general/layout.tsx b/src/app/[orgId]/settings/general/layout.tsx index 53d03918..a3f7264f 100644 --- a/src/app/[orgId]/settings/general/layout.tsx +++ b/src/app/[orgId]/settings/general/layout.tsx @@ -55,14 +55,12 @@ export default async function GeneralSettingsPage({ { title: t("security"), href: `/{orgId}/settings/general/security` - } - ]; - if (build !== "oss") { - navItems.push({ + }, + { title: t("authPage"), href: `/{orgId}/settings/general/auth-page` - }); - } + } + ]; return ( <> diff --git a/src/app/[orgId]/settings/general/security/page.tsx b/src/app/[orgId]/settings/general/security/page.tsx index 716e35d6..47946415 100644 --- a/src/app/[orgId]/settings/general/security/page.tsx +++ b/src/app/[orgId]/settings/general/security/page.tsx @@ -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, @@ -110,7 +105,7 @@ export default function SecurityPage() { return ( - {build !== "oss" && } + ); } @@ -243,144 +238,120 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { )} /> - {build !== "oss" && ( - <> - + - { - const isDisabled = !isPaidUser; + { + const isDisabled = !isPaidUser; - return ( - - - {t( - "logRetentionAccessLabel" - )} - - - { + if (!isDisabled) { + field.onChange( + parseInt( + value, + 10 + ) + ); + } + }} + disabled={isDisabled} + > + + + + + {LOG_RETENTION_OPTIONS.map( + (option) => ( + + {t( + option.label )} - /> - - - {LOG_RETENTION_OPTIONS.map( - ( - option - ) => ( - - {t( - option.label - )} - - ) - )} - - - - - - ); - }} - /> - { - const isDisabled = !isPaidUser; - - return ( - - - {t( - "logRetentionActionLabel" + + ) )} - - - + + + + ); + }} + /> + { + const isDisabled = !isPaidUser; + + return ( + + + {t("logRetentionActionLabel")} + + + - - - - ); - }} - /> - - )} + + ) + )} + + + + + + ); + }} + /> @@ -740,7 +711,7 @@ function SecuritySettingsSectionForm({ org }: SectionFormProps) { type="submit" form="security-settings-section-form" loading={loadingSave} - disabled={loadingSave} + disabled={loadingSave || !isPaidUser} > {t("saveSettings")} diff --git a/src/app/[orgId]/settings/logs/access/page.tsx b/src/app/[orgId]/settings/logs/access/page.tsx index d5b12ddb..6da993ba 100644 --- a/src/app/[orgId]/settings/logs/access/page.tsx +++ b/src/app/[orgId]/settings/logs/access/page.tsx @@ -20,6 +20,7 @@ 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"; export default function GeneralPage() { const router = useRouter(); @@ -209,7 +210,8 @@ export default function GeneralPage() { console.log("Date range changed:", { startDate, endDate, page, size }); if ( (build == "saas" && !subscription?.subscribed) || - (build == "enterprise" && !isUnlocked()) + (build == "enterprise" && !isUnlocked()) || + build === "oss" ) { console.log( "Access denied: subscription inactive or license locked" @@ -611,21 +613,7 @@ export default function GeneralPage() { description={t("accessLogsDescription")} /> - {build == "saas" && !subscription?.subscribed ? ( - - - {t("subscriptionRequiredToUse")} - - - ) : null} - - {build == "enterprise" && !isUnlocked() ? ( - - - {t("licenseRequiredToUse")} - - - ) : null} + diff --git a/src/app/[orgId]/settings/logs/action/page.tsx b/src/app/[orgId]/settings/logs/action/page.tsx index 344866bb..040a1920 100644 --- a/src/app/[orgId]/settings/logs/action/page.tsx +++ b/src/app/[orgId]/settings/logs/action/page.tsx @@ -2,6 +2,7 @@ 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"; @@ -92,6 +93,9 @@ export default function GeneralPage() { // Trigger search with default values on component mount useEffect(() => { + if (build === "oss") { + return; + } const defaultRange = getDefaultDateRange(); queryDateTime( defaultRange.startDate, @@ -461,21 +465,7 @@ export default function GeneralPage() { description={t("actionLogsDescription")} /> - {build == "saas" && !subscription?.subscribed ? ( - - - {t("subscriptionRequiredToUse")} - - - ) : null} - - {build == "enterprise" && !isUnlocked() ? ( - - - {t("licenseRequiredToUse")} - - - ) : null} + diff --git a/src/app/[orgId]/settings/logs/request/page.tsx b/src/app/[orgId]/settings/logs/request/page.tsx index 741dd994..4a1fe3cd 100644 --- a/src/app/[orgId]/settings/logs/request/page.tsx +++ b/src/app/[orgId]/settings/logs/request/page.tsx @@ -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, diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx index d8f050d3..b4dc3ad8 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/general/page.tsx @@ -63,6 +63,7 @@ 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"; type MaintenanceSectionFormProps = { resource: GetResourceResponse; @@ -78,6 +79,7 @@ function MaintenanceSectionForm({ const api = createApiClient({ env }); const { isUnlocked } = useLicenseStatusContext(); const subscription = useSubscriptionStatusContext(); + const { isPaidUser } = usePaidStatus(); const MaintenanceFormSchema = z.object({ maintenanceModeEnabled: z.boolean().optional(), @@ -161,7 +163,7 @@ function MaintenanceSectionForm({ const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked(); const isSaasNotSubscribed = build === "saas" && !subscription?.isSubscribed(); - return isEnterpriseNotLicensed || isSaasNotSubscribed; + return isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"; }; if (!resource.http) { @@ -413,7 +415,7 @@ function MaintenanceSectionForm({ - - - )} + + + + )} - {build === "enterprise" && ( - - - - )} + + + )} diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 5d1285da..854f6e19 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -121,24 +121,16 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ href: "/{orgId}/settings/access/roles", icon: }, - ...(build === "saas" || env?.flags.useOrgOnlyIdp - ? [ - { - title: "sidebarIdentityProviders", - href: "/{orgId}/settings/idp", - icon: - } - ] - : []), - ...(build !== "oss" - ? [ - { - title: "sidebarApprovals", - href: "/{orgId}/settings/access/approvals", - icon: - } - ] - : []), + { + title: "sidebarIdentityProviders", + href: "/{orgId}/settings/idp", + icon: + }, + { + title: "sidebarApprovals", + href: "/{orgId}/settings/access/approvals", + icon: + }, { title: "sidebarShareableLinks", href: "/{orgId}/settings/share-links", @@ -155,20 +147,16 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ href: "/{orgId}/settings/logs/request", icon: }, - ...(build != "oss" - ? [ - { - title: "sidebarLogsAccess", - href: "/{orgId}/settings/logs/access", - icon: - }, - { - title: "sidebarLogsAction", - href: "/{orgId}/settings/logs/action", - icon: - } - ] - : []) + { + title: "sidebarLogsAccess", + href: "/{orgId}/settings/logs/access", + icon: + }, + { + title: "sidebarLogsAction", + href: "/{orgId}/settings/logs/action", + icon: + } ]; const analytics = { diff --git a/src/components/ApprovalFeed.tsx b/src/components/ApprovalFeed.tsx index 4c6122c6..e587354b 100644 --- a/src/components/ApprovalFeed.tsx +++ b/src/components/ApprovalFeed.tsx @@ -30,6 +30,7 @@ import { import { Separator } from "./ui/separator"; import { InfoPopup } from "./ui/info-popup"; import { ApprovalsEmptyState } from "./ApprovalsEmptyState"; +import { usePaidStatus } from "@app/hooks/usePaidStatus"; export type ApprovalFeedProps = { orgId: string; @@ -50,9 +51,12 @@ export function ApprovalFeed({ Object.fromEntries(searchParams.entries()) ); - const { data, isFetching, refetch } = useQuery( - approvalQueries.listApprovals(orgId, filters) - ); + const { isPaidUser } = usePaidStatus(); + + const { data, isFetching, refetch } = useQuery({ + ...approvalQueries.listApprovals(orgId, filters), + enabled: isPaidUser + }); const approvals = data?.approvals ?? []; @@ -209,19 +213,19 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) {   {approval.type === "user_device" && ( - {approval.deviceName ? ( - <> - {t("requestingNewDeviceApproval")}:{" "} - {approval.niceId ? ( - - {approval.deviceName} - - ) : ( - {approval.deviceName} - )} + {approval.deviceName ? ( + <> + {t("requestingNewDeviceApproval")}:{" "} + {approval.niceId ? ( + + {approval.deviceName} + + ) : ( + {approval.deviceName} + )} {approval.fingerprint && (
@@ -229,7 +233,10 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) { {t("deviceInformation")}
- {formatFingerprintInfo(approval.fingerprint, t)} + {formatFingerprintInfo( + approval.fingerprint, + t + )}
diff --git a/src/components/CreateRoleForm.tsx b/src/components/CreateRoleForm.tsx index ba9863b5..3ea56c53 100644 --- a/src/components/CreateRoleForm.tsx +++ b/src/components/CreateRoleForm.tsx @@ -160,56 +160,51 @@ export default function CreateRoleForm({ )} /> - {build !== "oss" && ( -
- - ( - - - + + ( + + + { + if ( + checked !== + "indeterminate" + ) { + form.setValue( + "requireDeviceApproval", checked - ) => { - if ( - checked !== - "indeterminate" - ) { - form.setValue( - "requireDeviceApproval", - checked - ); - } - }} - label={t( - "requireDeviceApproval" - )} - /> - + ); + } + }} + label={t( + "requireDeviceApproval" + )} + /> + - - {t( - "requireDeviceApprovalDescription" - )} - + + {t( + "requireDeviceApprovalDescription" + )} + - - - )} - /> -
- )} + + + )} + /> diff --git a/src/components/EditRoleForm.tsx b/src/components/EditRoleForm.tsx index 4e36fb27..81b5bef5 100644 --- a/src/components/EditRoleForm.tsx +++ b/src/components/EditRoleForm.tsx @@ -168,56 +168,50 @@ export default function EditRoleForm({ )} /> - {build !== "oss" && ( -
- + - ( - - - ( + + + { + if ( + checked !== + "indeterminate" + ) { + form.setValue( + "requireDeviceApproval", checked - ) => { - if ( - checked !== - "indeterminate" - ) { - form.setValue( - "requireDeviceApproval", - checked - ); - } - }} - label={t( - "requireDeviceApproval" - )} - /> - + ); + } + }} + label={t( + "requireDeviceApproval" + )} + /> + - - {t( - "requireDeviceApprovalDescription" - )} - + + {t( + "requireDeviceApprovalDescription" + )} + - - - )} - /> -
- )} + + + )} + /> diff --git a/src/components/PaidFeaturesAlert.tsx b/src/components/PaidFeaturesAlert.tsx index 30ba7d76..4fd1d0de 100644 --- a/src/components/PaidFeaturesAlert.tsx +++ b/src/components/PaidFeaturesAlert.tsx @@ -1,8 +1,16 @@ "use client"; -import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { Card, CardContent } from "@app/components/ui/card"; import { build } from "@server/build"; -import { useTranslations } from "next-intl"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; +import { ExternalLink, KeyRound, Sparkles } from "lucide-react"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; + +const bannerClassName = + "mb-6 border-primary/30 bg-linear-to-br from-primary/10 via-background to-background overflow-hidden"; +const bannerContentClassName = "py-3 px-4"; +const bannerRowClassName = + "flex items-center gap-2.5 text-sm text-muted-foreground"; export function PaidFeaturesAlert() { const t = useTranslations(); @@ -10,19 +18,50 @@ export function PaidFeaturesAlert() { return ( <> {build === "saas" && !hasSaasSubscription ? ( - - - {t("subscriptionRequiredToUse")} - - + + +
+ + {t("subscriptionRequiredToUse")} +
+
+
) : null} {build === "enterprise" && !hasEnterpriseLicense ? ( - - - {t("licenseRequiredToUse")} - - + + +
+ + {t("licenseRequiredToUse")} +
+
+
+ ) : null} + + {build === "oss" && !hasEnterpriseLicense ? ( + + +
+ + + {t.rich("ossEnterpriseEditionRequired", { + enterpriseEditionLink: (chunks) => ( + + {chunks} + + + ) + })} + +
+
+
) : null} ); diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 9d1469f1..0a1cf287 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -190,7 +190,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { const approvalsRes = await api.get<{ data: { approvals: Array<{ approvalId: number; clientId: number }> }; }>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`); - + const approval = approvalsRes.data.data.approvals[0]; if (!approval) { @@ -232,7 +232,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { const approvalsRes = await api.get<{ data: { approvals: Array<{ approvalId: number; clientId: number }> }; }>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`); - + const approval = approvalsRes.data.data.approvals[0]; if (!approval) { @@ -548,7 +548,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { - {clientRow.approvalState === "pending" && build !== "oss" && ( + {clientRow.approvalState === "pending" && ( <> approveDevice(clientRow)} @@ -652,17 +652,10 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { } ]; - if (build === "oss") { - return allOptions.filter((option) => option.value !== "pending" && option.value !== "denied"); - } - return allOptions; }, [t]); const statusFilterDefaultValues = useMemo(() => { - if (build === "oss") { - return ["active"]; - } return ["active", "pending"]; }, []); diff --git a/src/middleware.ts b/src/middleware.ts index 727e2579..f3fbb930 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,29 +2,6 @@ import { NextRequest, NextResponse } from "next/server"; import { build } from "@server/build"; export function middleware(request: NextRequest) { - // If build is OSS, block access to private routes - if (build === "oss") { - const pathname = request.nextUrl.pathname; - - // Define private route patterns that should be blocked in OSS build - const privateRoutes = [ - "/settings/billing", - "/settings/remote-exit-nodes", - "/settings/idp", - "/auth/org" - ]; - - // Check if current path matches any private route pattern - const isPrivateRoute = privateRoutes.some((route) => - pathname.includes(route) - ); - - if (isPrivateRoute) { - // Return 404 to make it seem like the route doesn't exist - return new NextResponse(null, { status: 404 }); - } - } - return NextResponse.next(); }