mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add premium license banner for custom roles page (#14595)
* feat: initial commit for premium banners * feat: update design and copy * fix: fix format * fix: cleanup * fix: paywall stories and popoverpaywall * fix: updates for review comments * chore: remove references to enterprise license * chore:n remove references to enterprise license * fix: format * chore: simplify branding colors * fix: fix color references
This commit is contained in:
@@ -4,12 +4,12 @@ import {
|
||||
Badges,
|
||||
DisabledBadge,
|
||||
EnabledBadge,
|
||||
EnterpriseBadge,
|
||||
EntitledBadge,
|
||||
HealthyBadge,
|
||||
NotHealthyBadge,
|
||||
NotReachableBadge,
|
||||
NotRegisteredBadge,
|
||||
PremiumBadge,
|
||||
PreviewBadge,
|
||||
} from "./Badges";
|
||||
|
||||
@@ -50,9 +50,9 @@ export const Disabled: Story = {
|
||||
children: <DisabledBadge />,
|
||||
},
|
||||
};
|
||||
export const Enterprise: Story = {
|
||||
export const Premium: Story = {
|
||||
args: {
|
||||
children: <EnterpriseBadge />,
|
||||
children: <PremiumBadge />,
|
||||
},
|
||||
};
|
||||
export const Preview: Story = {
|
||||
|
||||
@@ -106,31 +106,14 @@ export const DisabledBadge: FC = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export const EnterpriseBadge: FC = () => {
|
||||
return (
|
||||
<span
|
||||
css={[
|
||||
styles.badge,
|
||||
(theme) => ({
|
||||
backgroundColor: theme.roles.notice.background,
|
||||
border: `1px solid ${theme.roles.notice.outline}`,
|
||||
color: theme.roles.notice.text,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
Enterprise
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const PremiumBadge: FC = () => {
|
||||
return (
|
||||
<span
|
||||
css={[
|
||||
styles.badge,
|
||||
(theme) => ({
|
||||
backgroundColor: theme.roles.notice.background,
|
||||
border: `1px solid ${theme.roles.notice.outline}`,
|
||||
backgroundColor: theme.branding.background,
|
||||
border: `1px solid ${theme.branding.border}`,
|
||||
color: theme.roles.notice.text,
|
||||
}),
|
||||
]}
|
||||
|
||||
@@ -9,12 +9,10 @@ const meta: Meta<typeof Paywall> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Paywall>;
|
||||
|
||||
const Example: Story = {
|
||||
export const Premium: Story = {
|
||||
args: {
|
||||
message: "Black Lotus",
|
||||
description:
|
||||
"Adds 3 mana of any single color of your choice to your mana pool, then is discarded. Tapping this artifact can be played as an interrupt.",
|
||||
},
|
||||
};
|
||||
|
||||
export { Example as Paywall };
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Interpolation, Theme } from "@emotion/react";
|
||||
import TaskAltIcon from "@mui/icons-material/TaskAlt";
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import { EnterpriseBadge } from "components/Badges/Badges";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
@@ -19,11 +19,19 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
documentationLink,
|
||||
}) => {
|
||||
return (
|
||||
<div css={styles.root}>
|
||||
<div
|
||||
css={[
|
||||
styles.root,
|
||||
(theme) => ({
|
||||
backgroundImage: `linear-gradient(160deg, transparent, ${theme.branding.background})`,
|
||||
border: `1px solid ${theme.branding.border}`,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<div>
|
||||
<Stack direction="row" alignItems="center" css={{ marginBottom: 24 }}>
|
||||
<h5 css={styles.title}>{message}</h5>
|
||||
<EnterpriseBadge />
|
||||
<PremiumBadge />
|
||||
</Stack>
|
||||
|
||||
{description && <p css={styles.description}>{description}</p>}
|
||||
@@ -36,20 +44,24 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
Read the documentation
|
||||
</Link>
|
||||
</div>
|
||||
<div css={styles.separator}></div>
|
||||
<div css={styles.separator} />
|
||||
<Stack direction="column" alignItems="center" spacing={3}>
|
||||
<ul css={styles.featureList}>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> Template access control
|
||||
<FeatureIcon />
|
||||
High availability & workspace proxies
|
||||
</li>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> User groups
|
||||
<FeatureIcon />
|
||||
Multi-org & role-based access control
|
||||
</li>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> 24 hour support
|
||||
<FeatureIcon />
|
||||
24x7 global support with SLA
|
||||
</li>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> Audit logs
|
||||
<FeatureIcon />
|
||||
Unlimited Git & external auth integrations
|
||||
</li>
|
||||
</ul>
|
||||
<Button
|
||||
@@ -60,7 +72,7 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
variant="outlined"
|
||||
color="neutral"
|
||||
>
|
||||
Learn about Enterprise
|
||||
Learn about Premium
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -68,21 +80,25 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
};
|
||||
|
||||
const FeatureIcon: FC = () => {
|
||||
return <TaskAltIcon css={styles.featureIcon} />;
|
||||
return (
|
||||
<TaskAltIcon
|
||||
css={[
|
||||
(theme) => ({
|
||||
color: theme.branding.border,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
root: (theme) => ({
|
||||
root: () => ({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: 300,
|
||||
maxWidth: 920,
|
||||
margin: "auto",
|
||||
minHeight: 280,
|
||||
padding: 24,
|
||||
backgroundImage: `linear-gradient(160deg, transparent, ${theme.roles.active.background})`,
|
||||
border: `1px solid ${theme.roles.active.fill.outline}`,
|
||||
borderRadius: 8,
|
||||
gap: 32,
|
||||
}),
|
||||
@@ -92,25 +108,21 @@ const styles = {
|
||||
fontSize: 22,
|
||||
margin: 0,
|
||||
},
|
||||
description: (theme) => ({
|
||||
marginTop: 16,
|
||||
description: () => ({
|
||||
fontFamily: "inherit",
|
||||
maxWidth: 420,
|
||||
lineHeight: "160%",
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 16,
|
||||
maxWidth: 460,
|
||||
fontSize: 14,
|
||||
}),
|
||||
separator: (theme) => ({
|
||||
width: 1,
|
||||
height: 220,
|
||||
backgroundColor: theme.palette.divider,
|
||||
backgroundColor: theme.branding.divider,
|
||||
marginLeft: 8,
|
||||
}),
|
||||
featureList: {
|
||||
listStyle: "none",
|
||||
margin: 0,
|
||||
marginRight: 8,
|
||||
padding: "0 12px",
|
||||
padding: "0 24px",
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
@@ -9,19 +9,10 @@ const meta: Meta<typeof PopoverPaywall> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PopoverPaywall>;
|
||||
|
||||
export const Enterprise: Story = {
|
||||
args: {
|
||||
message: "Black Lotus",
|
||||
description:
|
||||
"Adds 3 mana of any single color of your choice to your mana pool, then is discarded. Tapping this artifact can be played as an interrupt.",
|
||||
},
|
||||
};
|
||||
|
||||
export const Premium: Story = {
|
||||
args: {
|
||||
message: "Black Lotus",
|
||||
description:
|
||||
"Adds 3 mana of any single color of your choice to your mana pool, then is discarded. Tapping this artifact can be played as an interrupt.",
|
||||
licenseType: "premium",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Interpolation, Theme } from "@emotion/react";
|
||||
import TaskAltIcon from "@mui/icons-material/TaskAlt";
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import { EnterpriseBadge, PremiumBadge } from "components/Badges/Badges";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
@@ -11,21 +11,27 @@ export interface PopoverPaywallProps {
|
||||
message: string;
|
||||
description?: ReactNode;
|
||||
documentationLink?: string;
|
||||
licenseType?: "enterprise" | "premium";
|
||||
}
|
||||
|
||||
export const PopoverPaywall: FC<PopoverPaywallProps> = ({
|
||||
message,
|
||||
description,
|
||||
documentationLink,
|
||||
licenseType = "enterprise",
|
||||
}) => {
|
||||
return (
|
||||
<div css={styles.root}>
|
||||
<div
|
||||
css={[
|
||||
styles.root,
|
||||
(theme) => ({
|
||||
backgroundImage: `linear-gradient(160deg, transparent, ${theme.branding.background})`,
|
||||
border: `1px solid ${theme.branding.border}`,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<div>
|
||||
<Stack direction="row" alignItems="center" css={{ marginBottom: 18 }}>
|
||||
<h5 css={styles.title}>{message}</h5>
|
||||
{licenseType === "premium" ? <PremiumBadge /> : <EnterpriseBadge />}
|
||||
<PremiumBadge />
|
||||
</Stack>
|
||||
|
||||
{description && <p css={styles.description}>{description}</p>}
|
||||
@@ -38,26 +44,21 @@ export const PopoverPaywall: FC<PopoverPaywallProps> = ({
|
||||
Read the documentation
|
||||
</Link>
|
||||
</div>
|
||||
<div css={styles.separator}></div>
|
||||
<div css={styles.separator} />
|
||||
<Stack direction="column" alignItems="center" spacing={2}>
|
||||
<ul css={styles.featureList}>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> Template access control
|
||||
<FeatureIcon /> High availability & workspace proxies
|
||||
</li>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> User groups
|
||||
<FeatureIcon /> Multi-org & role-based access control
|
||||
</li>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> 24 hour support
|
||||
<FeatureIcon /> 24x7 global support with SLA
|
||||
</li>
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> Audit logs
|
||||
<FeatureIcon /> Unlimited Git & external auth integrations
|
||||
</li>
|
||||
{licenseType === "premium" && (
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon /> Organizations
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<Button
|
||||
href={docs("/enterprise")}
|
||||
@@ -67,7 +68,7 @@ export const PopoverPaywall: FC<PopoverPaywallProps> = ({
|
||||
variant="outlined"
|
||||
color="neutral"
|
||||
>
|
||||
Learn about {licenseType === "premium" ? "Premium" : "Enterprise"}
|
||||
Learn about Premium
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -75,7 +76,15 @@ export const PopoverPaywall: FC<PopoverPaywallProps> = ({
|
||||
};
|
||||
|
||||
const FeatureIcon: FC = () => {
|
||||
return <TaskAltIcon css={styles.featureIcon} />;
|
||||
return (
|
||||
<TaskAltIcon
|
||||
css={[
|
||||
(theme) => ({
|
||||
color: theme.branding.border,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
@@ -85,8 +94,6 @@ const styles = {
|
||||
alignItems: "center",
|
||||
maxWidth: 600,
|
||||
padding: "24px 36px",
|
||||
backgroundImage: `linear-gradient(160deg, transparent, ${theme.roles.active.background})`,
|
||||
border: `1px solid ${theme.roles.active.fill.outline}`,
|
||||
borderRadius: 8,
|
||||
gap: 18,
|
||||
}),
|
||||
@@ -118,10 +125,6 @@ const styles = {
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
},
|
||||
featureIcon: (theme) => ({
|
||||
color: theme.roles.active.fill.outline,
|
||||
fontSize: "1.5em",
|
||||
}),
|
||||
feature: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -138,7 +138,7 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
|
||||
<Cond>
|
||||
<Paywall
|
||||
message="Audit logs"
|
||||
description="Audit logs allow you to monitor user operations on your deployment. You need an Enterprise license to use this feature."
|
||||
description="Audit logs allow you to monitor user operations on your deployment. You need an Premium license to use this feature."
|
||||
documentationLink={docs("/admin/audit-logs")}
|
||||
/>
|
||||
</Cond>
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@ import type { UpdateAppearanceConfig } from "api/typesGenerated";
|
||||
import {
|
||||
Badges,
|
||||
DisabledBadge,
|
||||
EnterpriseBadge,
|
||||
EntitledBadge,
|
||||
PremiumBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { PopoverPaywall } from "components/Paywall/PopoverPaywall";
|
||||
import {
|
||||
@@ -64,13 +64,13 @@ export const AppearanceSettingsPageView: FC<
|
||||
<Popover mode="hover">
|
||||
<PopoverTrigger>
|
||||
<span>
|
||||
<EnterpriseBadge />
|
||||
<PremiumBadge />
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent css={{ transform: "translateY(-28px)" }}>
|
||||
<PopoverPaywall
|
||||
message="Appearance"
|
||||
description="With an Enterprise license, you can customize the appearance of your deployment."
|
||||
description="With a Premium license, you can customize the appearance of your deployment."
|
||||
documentationLink="https://coder.com/docs/admin/appearance"
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { EnterpriseBadge } from "components/Badges/Badges";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import type { FC } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
@@ -45,9 +45,9 @@ export const ExternalAuthSettingsPageView: FC<
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<Alert severity="info" actions={<EnterpriseBadge key="enterprise" />}>
|
||||
<Alert severity="info" actions={<PremiumBadge key="enterprise" />}>
|
||||
Integrating with multiple External authentication providers is an
|
||||
Enterprise feature.
|
||||
Premium feature.
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export const LicenseCard: FC<LicenseCardProps> = ({
|
||||
title="Confirm License Removal"
|
||||
confirmLoading={isRemoving}
|
||||
confirmText="Remove"
|
||||
description="Removing this license will disable all Enterprise features. You add a new license at any time."
|
||||
description="Removing this license will disable all Premium features. You add a new license at any time."
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -57,7 +57,7 @@ const LicensesSettingsPageView: FC<Props> = ({
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Licenses"
|
||||
description="Manage licenses to unlock Enterprise features."
|
||||
description="Manage licenses to unlock Premium features."
|
||||
/>
|
||||
|
||||
<Stack direction="row" spacing={2}>
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import {
|
||||
Badges,
|
||||
DisabledBadge,
|
||||
EnabledBadge,
|
||||
EnterpriseBadge,
|
||||
PremiumBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
@@ -34,7 +34,7 @@ export const ObservabilitySettingsPageView: FC<
|
||||
|
||||
<Badges>
|
||||
{featureAuditLogEnabled ? <EnabledBadge /> : <DisabledBadge />}
|
||||
<EnterpriseBadge />
|
||||
<PremiumBadge />
|
||||
</Badges>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
Badges,
|
||||
DisabledBadge,
|
||||
EnabledBadge,
|
||||
EnterpriseBadge,
|
||||
PremiumBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
@@ -56,7 +56,7 @@ export const SecuritySettingsPageView: FC<SecuritySettingsPageViewProps> = ({
|
||||
|
||||
<Badges>
|
||||
{featureBrowserOnlyEnabled ? <EnabledBadge /> : <DisabledBadge />}
|
||||
<EnterpriseBadge />
|
||||
<PremiumBadge />
|
||||
</Badges>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
<Cond condition={!isTemplateRBACEnabled}>
|
||||
<Paywall
|
||||
message="Groups"
|
||||
description="Organize users into groups with restricted access to templates. You need an Enterprise license to use this feature."
|
||||
description="Organize users into groups with restricted access to templates. You need an Premium license to use this feature."
|
||||
documentationLink={docs("/admin/groups")}
|
||||
/>
|
||||
</Cond>
|
||||
@@ -58,7 +58,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
<TableRow>
|
||||
<TableCell width="50%">Name</TableCell>
|
||||
<TableCell width="49%">Users</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
<TableCell width="1%" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
@@ -86,7 +86,6 @@ export const CreateOrganizationPageView: FC<
|
||||
message="Organizations"
|
||||
description="Organizations allow you to run a Coder deployment with multiple platform teams, all with unique use cases, templates, and even underlying infrastructure."
|
||||
documentationLink={docs("/guides/using-organizations")}
|
||||
licenseType="premium"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -21,6 +21,14 @@ export const NotEnabled: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const NotEnabledEmptyTable: Story = {
|
||||
args: {
|
||||
roles: [],
|
||||
canAssignOrgRole: true,
|
||||
isCustomRolesEnabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Enabled: Story = {
|
||||
args: {
|
||||
roles: [MockRoleWithOrgPermissions],
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ThreeDotsButton,
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
import { Paywall } from "components/Paywall/Paywall";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
@@ -44,75 +45,74 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
|
||||
const isLoading = roles === undefined;
|
||||
const isEmpty = Boolean(roles && roles.length === 0);
|
||||
return (
|
||||
<>
|
||||
<ChooseOne>
|
||||
<Cond condition={!isCustomRolesEnabled}>
|
||||
<Paywall
|
||||
message="Custom Roles"
|
||||
description="Create custom roles to assign a specific set of permissions to a user. You need an Enterprise license to use this feature."
|
||||
documentationLink={docs("/admin/groups")}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<Stack spacing={4}>
|
||||
{!isCustomRolesEnabled && (
|
||||
<Paywall
|
||||
message="Custom Roles"
|
||||
description="Custom Roles are available in the Premium plan and require an upgraded license. Please contact your Customer Success team for more information."
|
||||
documentationLink={docs("/admin/groups")}
|
||||
/>
|
||||
)}
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="40%">Name</TableCell>
|
||||
<TableCell width="59%">Permissions</TableCell>
|
||||
<TableCell width="1%" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<ChooseOne>
|
||||
<Cond condition={isLoading}>
|
||||
<TableLoader />
|
||||
</Cond>
|
||||
|
||||
<Cond condition={isEmpty}>
|
||||
<TableRow>
|
||||
<TableCell width="40%">Name</TableCell>
|
||||
<TableCell width="59%">Permissions</TableCell>
|
||||
<TableCell width="1%" />
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No custom roles yet"
|
||||
description={
|
||||
canAssignOrgRole && isCustomRolesEnabled
|
||||
? "Create your first custom role"
|
||||
: !isCustomRolesEnabled
|
||||
? "Upgrade to a premium license to create a custom role"
|
||||
: "You don't have permission to create a custom role"
|
||||
}
|
||||
cta={
|
||||
canAssignOrgRole &&
|
||||
isCustomRolesEnabled && (
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="create"
|
||||
startIcon={<AddOutlined />}
|
||||
variant="contained"
|
||||
>
|
||||
Create custom role
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<ChooseOne>
|
||||
<Cond condition={isLoading}>
|
||||
<TableLoader />
|
||||
</Cond>
|
||||
</Cond>
|
||||
|
||||
<Cond condition={isEmpty}>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="No custom roles yet"
|
||||
description={
|
||||
canAssignOrgRole
|
||||
? "Create your first custom role"
|
||||
: "You don't have permission to create a custom role"
|
||||
}
|
||||
cta={
|
||||
canAssignOrgRole && (
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="create"
|
||||
startIcon={<AddOutlined />}
|
||||
variant="contained"
|
||||
>
|
||||
Create custom role
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Cond>
|
||||
|
||||
<Cond>
|
||||
{roles?.map((role) => (
|
||||
<RoleRow
|
||||
key={role.name}
|
||||
role={role}
|
||||
canAssignOrgRole={canAssignOrgRole}
|
||||
onDelete={() => onDeleteRole(role)}
|
||||
/>
|
||||
))}
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
</>
|
||||
<Cond>
|
||||
{roles?.map((role) => (
|
||||
<RoleRow
|
||||
key={role.name}
|
||||
role={role}
|
||||
canAssignOrgRole={canAssignOrgRole}
|
||||
onDelete={() => onDeleteRole(role)}
|
||||
/>
|
||||
))}
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
<Cond condition={!isTemplateRBACEnabled}>
|
||||
<Paywall
|
||||
message="Groups"
|
||||
description="Organize users into groups with restricted access to templates. You need an Enterprise license to use this feature."
|
||||
description="Organize users into groups with restricted access to templates. You need a Premium license to use this feature."
|
||||
documentationLink={docs("/admin/groups")}
|
||||
/>
|
||||
</Cond>
|
||||
@@ -58,7 +58,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
<TableRow>
|
||||
<TableCell width="50%">Name</TableCell>
|
||||
<TableCell width="49%">Users</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
<TableCell width="1%" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({ oidcConfig }) => {
|
||||
<Cond condition={false}>
|
||||
<Paywall
|
||||
message="IdP Sync"
|
||||
description="Configure group and role mappings to manage permissions outside of Coder."
|
||||
description="Configure group and role mappings to manage permissions outside of Coder. You need an Premium license to use this feature."
|
||||
documentationLink={docs("/admin/groups")}
|
||||
/>
|
||||
</Cond>
|
||||
|
||||
+7
-7
@@ -8,7 +8,7 @@ import {
|
||||
type UpdateTemplateMeta,
|
||||
WorkspaceAppSharingLevels,
|
||||
} from "api/typesGenerated";
|
||||
import { EnterpriseBadge } from "components/Badges/Badges";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import {
|
||||
FormFields,
|
||||
FormFooter,
|
||||
@@ -212,8 +212,8 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
alignItems="center"
|
||||
css={{ marginTop: 16 }}
|
||||
>
|
||||
<EnterpriseBadge />
|
||||
<span>Enterprise license required to enabled.</span>
|
||||
<PremiumBadge />
|
||||
<span>Premium license required to enabled.</span>
|
||||
</Stack>
|
||||
)}
|
||||
</StackLabelHelperText>
|
||||
@@ -241,9 +241,9 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
/>
|
||||
{!accessControlEnabled && (
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<EnterpriseBadge />
|
||||
<PremiumBadge />
|
||||
<FormHelperText>
|
||||
Enterprise license required to deprecate templates.
|
||||
Premium license required to deprecate templates.
|
||||
{template.deprecated &&
|
||||
" You cannot change the message, but you may remove it to mark this template as no longer deprecated."}
|
||||
</FormHelperText>
|
||||
@@ -281,9 +281,9 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
</TextField>
|
||||
{!portSharingControlsEnabled && (
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<EnterpriseBadge />
|
||||
<PremiumBadge />
|
||||
<FormHelperText>
|
||||
Enterprise license required to control max port sharing level.
|
||||
Premium license required to control max port sharing level.
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const TemplatePermissionsPage: FC = () => {
|
||||
{!isTemplateRBACEnabled ? (
|
||||
<Paywall
|
||||
message="Template permissions"
|
||||
description="Control access of templates for users and groups to templates. You need an Enterprise license to use this feature."
|
||||
description="Control access of templates for users and groups to templates. You need an Premium license to use this feature."
|
||||
documentationLink={docs("/admin/rbac")}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { GroupsByUserId } from "api/queries/groups";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { EnterpriseBadge } from "components/Badges/Badges";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { LastSeen } from "components/LastSeen/LastSeen";
|
||||
@@ -208,7 +208,7 @@ export const UsersTableBody: FC<UsersTableBodyProps> = ({
|
||||
disabled={!canViewActivity}
|
||||
>
|
||||
View activity
|
||||
{!canViewActivity && <EnterpriseBadge />}
|
||||
{!canViewActivity && <PremiumBadge />}
|
||||
</MoreMenuItem>
|
||||
<MoreMenuItem
|
||||
onClick={() => onResetUserPassword(user)}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface Branding {
|
||||
background: string;
|
||||
divider: string;
|
||||
border: string;
|
||||
text: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Branding } from "../branding";
|
||||
import colors from "../tailwindColors";
|
||||
|
||||
export default {
|
||||
background: colors.violet[950],
|
||||
divider: colors.violet[900],
|
||||
border: colors.violet[400],
|
||||
text: colors.violet[50],
|
||||
} satisfies Branding;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { forDarkThemes } from "../externalImages";
|
||||
import branding from "./branding";
|
||||
import experimental from "./experimental";
|
||||
import monaco from "./monaco";
|
||||
import muiTheme from "./mui";
|
||||
@@ -8,6 +9,7 @@ export default {
|
||||
...muiTheme,
|
||||
externalImages: forDarkThemes,
|
||||
experimental,
|
||||
branding,
|
||||
monaco,
|
||||
roles,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Branding } from "../branding";
|
||||
import colors from "../tailwindColors";
|
||||
|
||||
export default {
|
||||
background: colors.violet[950],
|
||||
divider: colors.violet[900],
|
||||
border: colors.violet[400],
|
||||
text: colors.violet[50],
|
||||
} satisfies Branding;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { forDarkThemes } from "../externalImages";
|
||||
import branding from "./branding";
|
||||
import experimental from "./experimental";
|
||||
import monaco from "./monaco";
|
||||
import muiTheme from "./mui";
|
||||
@@ -8,6 +9,7 @@ export default {
|
||||
...muiTheme,
|
||||
externalImages: forDarkThemes,
|
||||
experimental,
|
||||
branding,
|
||||
monaco,
|
||||
roles,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// biome-ignore lint/nursery/noRestrictedImports: We still use `Theme` as a basis for our actual theme, for now.
|
||||
import type { Theme as MuiTheme } from "@mui/material/styles";
|
||||
import type * as monaco from "monaco-editor";
|
||||
import type { Branding } from "./branding";
|
||||
import dark from "./dark";
|
||||
import darkBlue from "./darkBlue";
|
||||
import type { NewTheme } from "./experimental";
|
||||
@@ -20,6 +21,10 @@ export interface Theme extends Omit<MuiTheme, "palette"> {
|
||||
|
||||
/** Theme properties that we're testing out but haven't committed to. */
|
||||
experimental: NewTheme;
|
||||
|
||||
//** Theme colors related to marketing */
|
||||
branding: Branding;
|
||||
|
||||
monaco: monaco.editor.IStandaloneThemeData;
|
||||
externalImages: ExternalImageModeStyles;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Branding } from "../branding";
|
||||
import colors from "../tailwindColors";
|
||||
|
||||
export default {
|
||||
background: colors.violet[100],
|
||||
divider: colors.violet[300],
|
||||
border: colors.violet[600],
|
||||
text: colors.violet[950],
|
||||
} satisfies Branding;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { forLightThemes } from "../externalImages";
|
||||
import branding from "./branding";
|
||||
import experimental from "./experimental";
|
||||
import monaco from "./monaco";
|
||||
import muiTheme from "./mui";
|
||||
@@ -8,6 +9,7 @@ export default {
|
||||
...muiTheme,
|
||||
externalImages: forLightThemes,
|
||||
experimental,
|
||||
branding,
|
||||
monaco,
|
||||
roles,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user