Add Security upsell rows and Collections upsell page

Surfaces more Enterprise-only features in the community edition:

- Adds locked upsell rows to the Security settings page for two-factor
  authentication, automatic sign-out, and an IP allowlist. These only render
  on self-hosted instances, where the real settings are not available.
- Adds a Collections placeholder page to the upsell plugin advertising
  workspace-wide collection administration.

Relocates the shared EnterpriseBadge and UpsellSettingRow components from the
plugin into app/scenes/Settings/components so core settings pages and the
plugin can both consume them without inverting the app/plugin dependency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ca46gcmqU5HGgoK8SFHUx
This commit is contained in:
Claude
2026-08-02 15:00:41 +00:00
parent 2992b81bf7
commit 48a3d4e03d
6 changed files with 93 additions and 4 deletions
+28
View File
@@ -18,6 +18,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import isCloudHosted from "~/utils/isCloudHosted";
import SettingRow from "./components/SettingRow";
import { UpsellSettingRow } from "./components/UpsellSettingRow";
function Security() {
const { dialogs } = useStores();
@@ -312,6 +313,24 @@ function Security() {
onChange={handlePasskeysEnabledChange}
/>
</SettingRow>
{!isCloudHosted && (
<>
<UpsellSettingRow
name="twoFactorRequired"
label={t("Two-factor authentication")}
description={t(
"Require members to configure two-factor authentication to access the workspace."
)}
/>
<UpsellSettingRow
name="sessionTimeout"
label={t("Automatic sign-out")}
description={t(
"Automatically sign out members after a configurable period of inactivity."
)}
/>
</>
)}
<Heading as="h2">{t("Behavior")}</Heading>
<SettingRow
@@ -412,6 +431,15 @@ function Security() {
/>
</SettingRow>
)}
{!isCloudHosted && (
<UpsellSettingRow
name="ipAllowlist"
label={t("IP allowlist")}
description={t(
"Restrict workspace access to an approved list of IP addresses or ranges."
)}
/>
)}
</Scene>
);
}
@@ -2,8 +2,8 @@ import * as React from "react";
import styled from "styled-components";
import Flex from "~/components/Flex";
import Switch from "~/components/Switch";
import SettingRow from "~/scenes/Settings/components/SettingRow";
import { EnterpriseBadge } from "./EnterpriseBadge";
import SettingRow from "./SettingRow";
type Props = {
/** A unique name for the row, used to associate the label. */
+42
View File
@@ -0,0 +1,42 @@
import { CollectionIcon } from "outline-icons";
import { useTranslation } from "react-i18next";
import { UpsellScene } from "./components/UpsellScene";
/**
* Upsell placeholder for the Enterprise-only collection management page, which
* lets workspace admins administer the permissions and settings of every
* collection, including private ones.
*
* @returns The rendered upsell scene.
*/
export default function Collections() {
const { t } = useTranslation();
return (
<UpsellScene
title={t("Collections")}
icon={<CollectionIcon />}
description={t(
"Manage the permissions and settings of all collections in the knowledge base. As a workspace admin you can also administer private collections."
)}
features={[
{
name: "collections-administer-private",
label: t("Administer private collections"),
description: t(
"View and manage collections that have not been explicitly shared with you."
),
control: "none",
},
{
name: "collections-bulk-permissions",
label: t("Bulk permissions"),
description: t(
"Review and update sharing and access for every collection from one place."
),
control: "none",
},
]}
/>
);
}
@@ -10,8 +10,8 @@ import Heading from "~/components/Heading";
import Scene from "~/components/Scene";
import Text from "~/components/Text";
import env from "~/env";
import { EnterpriseBadge } from "./EnterpriseBadge";
import { UpsellSettingRow } from "./UpsellSettingRow";
import { EnterpriseBadge } from "~/scenes/Settings/components/EnterpriseBadge";
import { UpsellSettingRow } from "~/scenes/Settings/components/UpsellSettingRow";
/** A single Enterprise-only setting to preview within an upsell page. */
export type UpsellFeature = {
+20 -1
View File
@@ -1,4 +1,9 @@
import { BuildingBlocksIcon, HistoryIcon, SearchIcon } from "outline-icons";
import {
BuildingBlocksIcon,
CollectionIcon,
HistoryIcon,
SearchIcon,
} from "outline-icons";
import { createLazyComponent } from "~/components/LazyLoad";
import { Hook, PluginManager } from "~/utils/PluginManager";
import config from "../plugin.json";
@@ -18,6 +23,20 @@ PluginManager.add([
component: createLazyComponent(() => import("./AuditLog")),
},
},
{
...config,
id: "upsell-collections",
name: "Collections",
type: Hook.Settings,
value: {
group: "Workspace",
after: "Groups",
icon: CollectionIcon,
description:
"Manage permissions and settings for every collection, including private ones. Available in the Enterprise edition.",
component: createLazyComponent(() => import("./Collections")),
},
},
{
...config,
id: "upsell-data-attributes",