diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index f2765a3ec97..88bfebbdc75 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -10,6 +10,8 @@ MacOS = "macOS" AKS = "AKS" O_WRONLY = "O_WRONLY" AIBridge = "AI Bridge" +# plural of SIEM, as in security information and event management +SIEMs = "SIEMs" [default.extend-words] AKS = "AKS" diff --git a/site/src/components/Paywall/Paywall.tsx b/site/src/components/Paywall/Paywall.tsx index 4d1a1c81001..a13ffb3386c 100644 --- a/site/src/components/Paywall/Paywall.tsx +++ b/site/src/components/Paywall/Paywall.tsx @@ -1,4 +1,4 @@ -import { ArrowRightIcon, CheckIcon, ExternalLinkIcon } from "lucide-react"; +import { ArrowRightIcon, CheckIcon } from "lucide-react"; import type React from "react"; import type { FC } from "react"; import { type LinkProps, Link as RouterLink } from "react-router"; @@ -14,10 +14,9 @@ export const PREMIUM_FEATURES = [ ]; export const PREMIUM_PAGE_PATH = "/deployment/premium"; -export const PREMIUM_PRICING_LINK = "https://coder.com/pricing"; export const PREMIUM_DEFAULT_DESCRIPTION = "You need a Premium license to use this feature."; -export const PREMIUM_DEFAULT_HERO = "Get Access with Coder Premium"; +export const PREMIUM_DEFAULT_HERO = "Get access with a Coder trial"; export type PaywallProps = React.ComponentProps<"div"> & { message: string; @@ -113,25 +112,6 @@ export const PaywallDescription: FC> = ({ ); }; -export const PaywallDocumentationLink: FC> = ({ - children = "Read the documentation", - className, - href, - ...props -}) => { - return ( - - {children} - - ); -}; - export const PaywallSupergraphic: FC> = ({ className, ...props diff --git a/site/src/components/Paywall/PaywallPremium.stories.tsx b/site/src/components/Paywall/PaywallPremium.stories.tsx index a72e4fbbced..7344046e2e5 100644 --- a/site/src/components/Paywall/PaywallPremium.stories.tsx +++ b/site/src/components/Paywall/PaywallPremium.stories.tsx @@ -1,6 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, within } from "storybook/test"; -import { PREMIUM_PRICING_LINK } from "#/components/Paywall/Paywall"; import { PaywallPremium } from "./PaywallPremium"; const meta: Meta = { @@ -21,21 +20,20 @@ export const CanViewLicenses: Story = { const canvas = within(canvasElement); await expect( - canvas.getByRole("heading", { name: /Coder Premium/ }), + canvas.getByRole("heading", { name: /Get access with a Coder trial/ }), + ).toBeVisible(); + await expect( + canvas.getByText("Start an unlimited 30-day trial today"), ).toBeVisible(); - await expect(canvas.getByText("Start a 30-day trial today.")).toBeVisible(); const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toBeVisible(); await expect(cta).toHaveAttribute("href", "/deployment/premium"); await expect(cta).not.toHaveAttribute("target", "_blank"); - const pricing = canvas.getByRole("link", { - name: "Learn more about premium", - }); - await expect(pricing).toBeVisible(); - await expect(pricing).toHaveAttribute("href", PREMIUM_PRICING_LINK); - await expect(pricing).toHaveAttribute("target", "_blank"); + await expect( + canvas.queryByRole("link", { name: "Learn more about premium" }), + ).not.toBeInTheDocument(); await expect( canvas.getByRole("heading", { @@ -65,8 +63,8 @@ export const CannotViewLicenses: Story = { canvas.queryByRole("link", { name: "Start trial for free" }), ).not.toBeInTheDocument(); await expect( - canvas.getByRole("link", { name: "Learn more about premium" }), - ).toBeVisible(); + canvas.queryByRole("link", { name: "Learn more about premium" }), + ).not.toBeInTheDocument(); }, }; diff --git a/site/src/components/Paywall/PaywallPremium.tsx b/site/src/components/Paywall/PaywallPremium.tsx index cc3638cb76c..a480efa66ad 100644 --- a/site/src/components/Paywall/PaywallPremium.tsx +++ b/site/src/components/Paywall/PaywallPremium.tsx @@ -1,4 +1,3 @@ -import { ExternalLinkIcon } from "lucide-react"; import type { FC } from "react"; import { Supergraphic } from "#/components/Supergraphic/Supergraphic"; import { cn } from "#/utils/cn"; @@ -13,10 +12,9 @@ import { PREMIUM_DEFAULT_HERO, PREMIUM_FEATURES, PREMIUM_PAGE_PATH, - PREMIUM_PRICING_LINK, } from "./Paywall"; -const DEFAULT_HERO_SUBTITLE = "Start a 30-day trial today."; +const DEFAULT_HERO_SUBTITLE = "Start an unlimited 30-day trial today"; const PaywallPremiumHeader: FC> = ({ children, @@ -88,15 +86,6 @@ const PaywallPremium = ({

{description}

- - Learn more about premium - {features.map((feature) => ( diff --git a/site/src/components/Paywall/PaywallSmall.stories.tsx b/site/src/components/Paywall/PaywallSmall.stories.tsx index a0cefd14ba0..b96d238db4c 100644 --- a/site/src/components/Paywall/PaywallSmall.stories.tsx +++ b/site/src/components/Paywall/PaywallSmall.stories.tsx @@ -37,8 +37,8 @@ export const CannotViewLicenses: Story = { const canvas = within(canvasElement); await expect( - canvas.getByRole("link", { name: "Learn more about premium" }), - ).toBeVisible(); + canvas.queryByRole("link", { name: "Learn more about premium" }), + ).not.toBeInTheDocument(); await expect( canvas.getByText(/contact your deployment administrator/i), ).toBeVisible(); diff --git a/site/src/components/Paywall/PaywallSmall.tsx b/site/src/components/Paywall/PaywallSmall.tsx index e357840f4d2..5833f3c518d 100644 --- a/site/src/components/Paywall/PaywallSmall.tsx +++ b/site/src/components/Paywall/PaywallSmall.tsx @@ -4,7 +4,6 @@ import { PaywallContent, PaywallCTALink, PaywallDescription, - PaywallDocumentationLink, PaywallFeature, PaywallFeatures, PaywallGuidance, @@ -16,7 +15,6 @@ import { PREMIUM_DEFAULT_HERO, PREMIUM_FEATURES, PREMIUM_PAGE_PATH, - PREMIUM_PRICING_LINK, } from "./Paywall"; const PaywallSmall = ({ @@ -50,9 +48,6 @@ const PaywallSmall = ({ > {description} - - Learn more about premium - diff --git a/site/src/pages/AIBridgePage/AIBridgeSessionsLayout.tsx b/site/src/pages/AIBridgePage/AIBridgeSessionsLayout.tsx index 0f40f32d3ab..31a27c1a1a4 100644 --- a/site/src/pages/AIBridgePage/AIBridgeSessionsLayout.tsx +++ b/site/src/pages/AIBridgePage/AIBridgeSessionsLayout.tsx @@ -1,18 +1,22 @@ import type { FC, PropsWithChildren } from "react"; import { Outlet } from "react-router"; -import { Link } from "#/components/Link/Link"; import { Margins } from "#/components/Margins/Margins"; import { PageHeader, PageHeaderSubtitle, PageHeaderTitle, } from "#/components/PageHeader/PageHeader"; +import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader"; import { docs } from "#/utils/docs"; const AIBridgeSessionsLayout: FC = () => { return ( - + + } + >
AI Sessions @@ -20,14 +24,7 @@ const AIBridgeSessionsLayout: FC = () => { Review and audit AI activity, token usage, and prompt history across - sessions.{" "} - - Learn how to audit AI sessions - + sessions. diff --git a/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.stories.tsx b/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.stories.tsx index 593263a66c3..cf2d6169770 100644 --- a/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.stories.tsx +++ b/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.stories.tsx @@ -5,6 +5,7 @@ import { MockPermissions, mockApiError, } from "#/testHelpers/entities"; +import { docs } from "#/utils/docs"; import { GatewayKeysPageView } from "./GatewayKeysPageView"; const meta: Meta = { @@ -81,6 +82,12 @@ export const Paywall: Story = { const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toHaveAttribute("href", "/deployment/premium"); + await expect( + canvas.getByRole("link", { name: /Read the docs/ }), + ).toHaveAttribute( + "href", + docs("/ai-coder/ai-gateway/standalone#create-a-gateway-key"), + ); }, }; diff --git a/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.tsx b/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.tsx index 5c151d415b5..9708010aaa7 100644 --- a/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.tsx +++ b/site/src/pages/AISettingsPage/GatewayKeysPage/GatewayKeysPageView.tsx @@ -3,11 +3,11 @@ import type { FC } from "react"; import type { AIGatewayKey } from "#/api/typesGenerated"; import { ErrorAlert } from "#/components/Alert/ErrorAlert"; import { Button } from "#/components/Button/Button"; -import { Link } from "#/components/Link/Link"; import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; import { SettingsHeader, SettingsHeaderDescription, + SettingsHeaderDocsLink, SettingsHeaderTitle, } from "#/components/SettingsHeader/SettingsHeader"; import { @@ -47,19 +47,25 @@ export const GatewayKeysPageView: FC = ({
- - Create key - - ) +
+ + {!showPaywall && ( + + )} +
} > AI Gateway Keys Keys authenticate standalone AI Gateway replicas to this deployment. - The key value is shown only once when created.{" "} - View docs + The key value is shown only once when created.
diff --git a/site/src/pages/AuditPage/AuditPageView.stories.tsx b/site/src/pages/AuditPage/AuditPageView.stories.tsx index d9d94536cb0..030ed84c0c5 100644 --- a/site/src/pages/AuditPage/AuditPageView.stories.tsx +++ b/site/src/pages/AuditPage/AuditPageView.stories.tsx @@ -18,6 +18,7 @@ import { MockUserOwner, } from "#/testHelpers/entities"; import { pixelWithTablet } from "#/testHelpers/pixel"; +import { docs } from "#/utils/docs"; import { useResourceTypeFilterMenu } from "./AuditFilter"; import { AuditPageView } from "./AuditPageView"; @@ -100,6 +101,9 @@ export const NotVisible: Story = { const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toHaveAttribute("href", "/deployment/premium"); + await expect( + canvas.getByRole("link", { name: /Read the docs/ }), + ).toHaveAttribute("href", docs("/admin/security/audit-logs")); }, }; diff --git a/site/src/pages/AuditPage/AuditPageView.tsx b/site/src/pages/AuditPage/AuditPageView.tsx index 2ed54a7fa3e..c6f9062253a 100644 --- a/site/src/pages/AuditPage/AuditPageView.tsx +++ b/site/src/pages/AuditPage/AuditPageView.tsx @@ -11,11 +11,13 @@ import { type PaginationResult, } from "#/components/PaginationWidget/PaginationContainer"; import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; +import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader"; import { Table, TableBody } from "#/components/Table/Table"; import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; import { TableLoader } from "#/components/TableLoader/TableLoader"; import { Timeline } from "#/components/Timeline/Timeline"; import type { Permissions } from "#/modules/permissions"; +import { docs } from "#/utils/docs"; import { AuditFilter } from "./AuditFilter"; import { AuditHelpPopover } from "./AuditHelpPopover"; import { AuditLogRow } from "./AuditLogRow/AuditLogRow"; @@ -49,7 +51,11 @@ export const AuditPageView: FC = ({ return ( - + + } + >
Audit diff --git a/site/src/pages/ConnectionLogPage/ConnectionLogPageView.stories.tsx b/site/src/pages/ConnectionLogPage/ConnectionLogPageView.stories.tsx index a981770aad9..ff238731a5c 100644 --- a/site/src/pages/ConnectionLogPage/ConnectionLogPageView.stories.tsx +++ b/site/src/pages/ConnectionLogPage/ConnectionLogPageView.stories.tsx @@ -17,6 +17,7 @@ import { MockUserOwner, } from "#/testHelpers/entities"; import { pixelWithTablet } from "#/testHelpers/pixel"; +import { docs } from "#/utils/docs"; import { ConnectionLogPageView } from "./ConnectionLogPageView"; type FilterProps = ComponentProps["filterProps"]; @@ -100,6 +101,9 @@ export const NotVisible: Story = { const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toHaveAttribute("href", "/deployment/premium"); + await expect( + canvas.getByRole("link", { name: /Read the docs/ }), + ).toHaveAttribute("href", docs("/admin/monitoring/connection-logs")); }, }; diff --git a/site/src/pages/ConnectionLogPage/ConnectionLogPageView.tsx b/site/src/pages/ConnectionLogPage/ConnectionLogPageView.tsx index 921ab0f4273..c0b9da41b89 100644 --- a/site/src/pages/ConnectionLogPage/ConnectionLogPageView.tsx +++ b/site/src/pages/ConnectionLogPage/ConnectionLogPageView.tsx @@ -11,11 +11,13 @@ import { type PaginationResult, } from "#/components/PaginationWidget/PaginationContainer"; import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; +import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader"; import { Table, TableBody } from "#/components/Table/Table"; import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; import { TableLoader } from "#/components/TableLoader/TableLoader"; import { Timeline } from "#/components/Timeline/Timeline"; import type { Permissions } from "#/modules/permissions"; +import { docs } from "#/utils/docs"; import { ConnectionLogFilter } from "./ConnectionLogFilter"; import { ConnectionLogHelpPopover } from "./ConnectionLogHelpPopover"; import { ConnectionLogRow } from "./ConnectionLogRow/ConnectionLogRow"; @@ -48,7 +50,13 @@ export const ConnectionLogPageView: FC = ({ return ( - + + } + >
Connection Log diff --git a/site/src/pages/DeploymentSettingsPage/AIGovernanceSettingsPage/AIGovernanceSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/AIGovernanceSettingsPage/AIGovernanceSettingsPageView.tsx index 00236f80e4a..f473196315e 100644 --- a/site/src/pages/DeploymentSettingsPage/AIGovernanceSettingsPage/AIGovernanceSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/AIGovernanceSettingsPage/AIGovernanceSettingsPageView.tsx @@ -31,7 +31,7 @@ export const AIGovernanceSettingsPageView: FC<
+ } > diff --git a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx index c917ccd729d..bc81d061c79 100644 --- a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, within } from "storybook/test"; import { MockPermissions } from "#/testHelpers/entities"; +import { docs } from "#/utils/docs"; import { AppearanceSettingsPageView } from "./AppearanceSettingsPageView"; const meta: Meta = { @@ -56,6 +57,9 @@ export const NotEntitled: Story = { const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toHaveAttribute("href", "/deployment/premium"); + await expect( + canvas.getByRole("link", { name: /Read the docs/ }), + ).toHaveAttribute("href", docs("/admin/setup/appearance")); await expect( canvas.queryByRole("form", { name: "Appearance settings" }), ).not.toBeInTheDocument(); diff --git a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx index 7c20768fc3b..dce55b3def7 100644 --- a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx @@ -14,9 +14,11 @@ import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; import { SettingsHeader, SettingsHeaderDescription, + SettingsHeaderDocsLink, SettingsHeaderTitle, } from "#/components/SettingsHeader/SettingsHeader"; import { Spinner } from "#/components/Spinner/Spinner"; +import { docs } from "#/utils/docs"; import { getFormHelpers } from "#/utils/formUtils"; import { AnnouncementBannerSettings } from "./AnnouncementBannerSettings"; @@ -47,7 +49,11 @@ export const AppearanceSettingsPageView: FC< return (
- + + } + > Appearance Customize the look and feel of your Coder deployment. diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx index e9e09002033..9b8c35fa2d6 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx @@ -7,9 +7,9 @@ import { organizationIdpSyncSettings, patchOrganizationSyncSettings, } from "#/api/queries/idpsync"; -import { Link } from "#/components/Link/Link"; import { Loader } from "#/components/Loader/Loader"; import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; +import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader"; import { useAuthenticated } from "#/hooks/useAuthenticated"; import { useDashboard } from "#/modules/dashboard/useDashboard"; import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; @@ -70,12 +70,14 @@ const IdpOrgSyncPage: FC = () => {

Automatically assign users to an organization based on their IdP claims. - - View docs -

- +
+ + +
{!isIdpSyncEnabled ? (
} + actions={ + + } > Observability diff --git a/site/src/pages/GroupsPage/GroupsPage.tsx b/site/src/pages/GroupsPage/GroupsPage.tsx index 993f30fc1eb..fcf2eff29e4 100644 --- a/site/src/pages/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/GroupsPage/GroupsPage.tsx @@ -14,12 +14,14 @@ import { Loader } from "#/components/Loader/Loader"; import { SettingsHeader, SettingsHeaderDescription, + SettingsHeaderDocsLink, SettingsHeaderTitle, } from "#/components/SettingsHeader/SettingsHeader"; import { useAuthenticated } from "#/hooks/useAuthenticated"; import { usePaginatedQuery } from "#/hooks/usePaginatedQuery"; import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; import { RequirePermission } from "#/modules/permissions/RequirePermission"; +import { docs } from "#/utils/docs"; import { pageTitle } from "#/utils/page"; import { useGroupsSettings } from "./GroupsPageProvider"; import { GroupsPageView, joinGroupsSpend } from "./GroupsPageView"; @@ -111,15 +113,17 @@ const GroupsPage: FC = () => {
{title} -
- - Groups - - Manage groups for this{" "} - {showOrganizations ? "organization" : "deployment"}. - - -
+ + } + > + Groups + + Manage groups for this{" "} + {showOrganizations ? "organization" : "deployment"}. + + = { @@ -35,8 +36,8 @@ export const NotEntitled: Story = { const canvas = within(canvasElement); await expect( - canvas.getByRole("link", { name: "Learn more about premium" }), - ).toBeVisible(); + canvas.getByRole("link", { name: /Read the docs/ }), + ).toHaveAttribute("href", docs("/admin/users/organizations")); const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toHaveAttribute("href", "/deployment/premium"); }, diff --git a/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx b/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx index eaa5d641a76..76b4a914861 100644 --- a/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx @@ -12,10 +12,12 @@ import { FormField } from "#/components/FormField/FormField"; import { IconField } from "#/components/IconField/IconField"; import { Label } from "#/components/Label/Label"; import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; +import { SettingsHeaderDocsLink } from "#/components/SettingsHeader/SettingsHeader"; import { Spinner } from "#/components/Spinner/Spinner"; import { Textarea } from "#/components/Textarea/Textarea"; import type { Permissions } from "#/modules/permissions"; import { cn } from "#/utils/cn"; +import { docs } from "#/utils/docs"; import { displayNameValidator, getFormHelpers, @@ -74,6 +76,9 @@ export const CreateOrganizationPageView: FC< Go Back
+
+ +
{Boolean(error) && !isApiValidationError(error) && ( diff --git a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx index c80cbd8d8d9..611117319c4 100644 --- a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx @@ -11,6 +11,7 @@ import { EmptyState } from "#/components/EmptyState/EmptyState"; import { SettingsHeader, SettingsHeaderDescription, + SettingsHeaderDocsLink, SettingsHeaderTitle, } from "#/components/SettingsHeader/SettingsHeader"; import { useAuthenticated } from "#/hooks/useAuthenticated"; @@ -18,6 +19,7 @@ import { useDashboard } from "#/modules/dashboard/useDashboard"; import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; import { useOrganizationSettings } from "#/modules/management/OrganizationSettingsLayout"; import { RequirePermission } from "#/modules/permissions/RequirePermission"; +import { docs } from "#/utils/docs"; import { pageTitle } from "#/utils/page"; import { CustomRolesPageView } from "./CustomRolesPageView"; @@ -80,14 +82,16 @@ const CustomRolesPage: FC = () => { -
- - Roles - - Manage roles for this organization. - - -
+ + } + > + Roles + + Manage roles for this organization. + + {

Automatically assign groups or roles to a user based on their IdP claims. - View docs

+ {!isIdpSyncEnabled ? ( - + } + > Provisioner Keys - Manage provisioner keys used to authenticate provisioner instances.{" "} - View docs + Manage provisioner keys used to authenticate provisioner instances. diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx index 5d953a3acb6..340a75608bd 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx @@ -8,6 +8,7 @@ import { MockUserProvisioner, mockApiError, } from "#/testHelpers/entities"; +import { docs } from "#/utils/docs"; import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView"; const meta: Meta = { @@ -75,6 +76,9 @@ export const Paywall: Story = { const cta = canvas.getByRole("link", { name: "Start trial for free" }); await expect(cta).toHaveAttribute("href", "/deployment/premium"); + await expect( + canvas.getByRole("link", { name: /Read the docs/ }), + ).toHaveAttribute("href", docs("/admin/provisioners")); }, }; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx index c164ced0bc8..5d291a40c2d 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx @@ -9,6 +9,7 @@ import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; import { SettingsHeader, SettingsHeaderDescription, + SettingsHeaderDocsLink, SettingsHeaderTitle, } from "#/components/SettingsHeader/SettingsHeader"; import { @@ -60,12 +61,13 @@ export const OrganizationProvisionersPageView: FC< }) => { return (
- + } + > Provisioners Coder server runs provisioner daemons which execute terraform during - workspace and template builds.{" "} - View docs + workspace and template builds. diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx index 6f172adfbbf..f2d87a851e6 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx @@ -8,8 +8,15 @@ import { templateACL, } from "#/api/queries/templates"; import { PaywallPremium } from "#/components/Paywall/PaywallPremium"; +import { + SettingsHeader, + SettingsHeaderDescription, + SettingsHeaderDocsLink, + SettingsHeaderTitle, +} from "#/components/SettingsHeader/SettingsHeader"; import { useAuthenticated } from "#/hooks/useAuthenticated"; import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; +import { docs } from "#/utils/docs"; import { pageTitle } from "#/utils/page"; import { useTemplateSettings } from "../TemplateSettingsLayout"; import { TemplatePermissionsPageView } from "./TemplatePermissionsPageView"; @@ -33,159 +40,174 @@ const TemplatePermissionsPage: FC = () => { <> Codestin Search App - {!isTemplateRBACEnabled ? ( - - ) : ( - { - await addUserMutation.mutateAsync({ - templateId: template.id, - userId: user.id, - role, - }); - reset(); - }} - isAddingUser={addUserMutation.isPending} - onUpdateUser={async (user, role) => { - await updateUserMutation.mutateAsync( - { +
+ + } + > + Permissions + + Manage which members and groups can use this template. + + + + {!isTemplateRBACEnabled ? ( + + ) : ( + { + await addUserMutation.mutateAsync({ templateId: template.id, userId: user.id, role, - }, - { - onSuccess: () => { - toast.success( - `Role for "${user.username}" updated to "${role}" successfully.`, - ); + }); + reset(); + }} + isAddingUser={addUserMutation.isPending} + onUpdateUser={async (user, role) => { + await updateUserMutation.mutateAsync( + { + templateId: template.id, + userId: user.id, + role, }, - onError: (error) => { - toast.error( - getErrorMessage( - error, - `Failed to update role for "${user.username}".`, - ), - { - description: getErrorDetail(error), - }, - ); + { + onSuccess: () => { + toast.success( + `Role for "${user.username}" updated to "${role}" successfully.`, + ); + }, + onError: (error) => { + toast.error( + getErrorMessage( + error, + `Failed to update role for "${user.username}".`, + ), + { + description: getErrorDetail(error), + }, + ); + }, }, - }, - ); - }} - updatingUserId={ - updateUserMutation.isPending - ? updateUserMutation.variables?.userId - : undefined - } - onRemoveUser={async (user) => { - await removeUserMutation.mutateAsync( - { - templateId: template.id, - userId: user.id, - role: "", - }, - { - onSuccess: () => { - toast.success( - `User "${user.username}" removed successfully.`, - ); + ); + }} + updatingUserId={ + updateUserMutation.isPending + ? updateUserMutation.variables?.userId + : undefined + } + onRemoveUser={async (user) => { + await removeUserMutation.mutateAsync( + { + templateId: template.id, + userId: user.id, + role: "", }, - onError: (error) => { - toast.error( - getErrorMessage( - error, - `Failed to remove user "${user.username}".`, - ), - { - description: getErrorDetail(error), - }, - ); + { + onSuccess: () => { + toast.success( + `User "${user.username}" removed successfully.`, + ); + }, + onError: (error) => { + toast.error( + getErrorMessage( + error, + `Failed to remove user "${user.username}".`, + ), + { + description: getErrorDetail(error), + }, + ); + }, }, - }, - ); - }} - onAddGroup={async (group, role, reset) => { - await addGroupMutation.mutateAsync({ - templateId: template.id, - groupId: group.id, - role, - }); - reset(); - }} - isAddingGroup={addGroupMutation.isPending} - onUpdateGroup={async (group, role) => { - await updateGroupMutation.mutateAsync( - { + ); + }} + onAddGroup={async (group, role, reset) => { + await addGroupMutation.mutateAsync({ templateId: template.id, groupId: group.id, role, - }, - { - onSuccess: () => { - toast.success( - `Role for "${group.display_name || group.name}" updated to "${role}" successfully.`, - ); + }); + reset(); + }} + isAddingGroup={addGroupMutation.isPending} + onUpdateGroup={async (group, role) => { + await updateGroupMutation.mutateAsync( + { + templateId: template.id, + groupId: group.id, + role, }, - onError: (error) => { - toast.error( - getErrorMessage( - error, - `Failed to update role for "${group.display_name || group.name}".`, - ), - { - description: getErrorDetail(error), - }, - ); + { + onSuccess: () => { + toast.success( + `Role for "${group.display_name || group.name}" updated to "${role}" successfully.`, + ); + }, + onError: (error) => { + toast.error( + getErrorMessage( + error, + `Failed to update role for "${group.display_name || group.name}".`, + ), + { + description: getErrorDetail(error), + }, + ); + }, }, - }, - ); - }} - updatingGroupId={ - updateGroupMutation.isPending - ? updateGroupMutation.variables?.groupId - : undefined - } - onRemoveGroup={async (group) => { - await removeGroupMutation.mutateAsync( - { - groupId: group.id, - templateId: template.id, - role: "", - }, - { - onSuccess: () => { - toast.success( - `Group "${group.display_name || group.name}" removed successfully.`, - ); + ); + }} + updatingGroupId={ + updateGroupMutation.isPending + ? updateGroupMutation.variables?.groupId + : undefined + } + onRemoveGroup={async (group) => { + await removeGroupMutation.mutateAsync( + { + groupId: group.id, + templateId: template.id, + role: "", }, - onError: (error) => { - toast.error( - getErrorMessage( - error, - `Failed to remove group "${group.display_name || group.name}".`, - ), - { - description: getErrorDetail(error), - }, - ); + { + onSuccess: () => { + toast.success( + `Group "${group.display_name || group.name}" removed successfully.`, + ); + }, + onError: (error) => { + toast.error( + getErrorMessage( + error, + `Failed to remove group "${group.display_name || group.name}".`, + ), + { + description: getErrorDetail(error), + }, + ); + }, }, - }, - ); - }} - /> - )} + ); + }} + /> + )} +
); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx index 764840d3a7e..ee0a9854fa4 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx @@ -24,11 +24,6 @@ import { SelectTrigger, SelectValue, } from "#/components/Select/Select"; -import { - SettingsHeader, - SettingsHeaderDescription, - SettingsHeaderTitle, -} from "#/components/SettingsHeader/SettingsHeader"; import { Spinner } from "#/components/Spinner/Spinner"; import { Table, @@ -227,13 +222,6 @@ export const TemplatePermissionsPageView: FC< }) => { return (
- - Permissions - - Manage which members and groups can use this template. - - -
{canUpdatePermissions && (