mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
chore: add paywall to provisioners page (#14803)
* chore: add paywall to provisioners page * きれい * move some things into the page view * I guess I'm not allowed to use proper nouns * :|
This commit is contained in:
committed by
GitHub
parent
aef400c2c5
commit
4dcf5ef323
@ -30,7 +30,7 @@ import { ProvisionerTag } from "./ProvisionerTag";
|
||||
type ProvisionerGroupType = "builtin" | "psk" | "key";
|
||||
|
||||
interface ProvisionerGroupProps {
|
||||
readonly buildInfo?: BuildInfoResponse;
|
||||
readonly buildInfo: BuildInfoResponse;
|
||||
readonly keyName: string;
|
||||
readonly keyTags: Record<string, string>;
|
||||
readonly type: ProvisionerGroupType;
|
||||
@ -80,7 +80,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
|
||||
let warnings = 0;
|
||||
let provisionersWithWarnings = 0;
|
||||
const provisionersWithWarningInfo = provisioners.map((it) => {
|
||||
const outOfDate = Boolean(buildInfo) && it.version !== buildInfo?.version;
|
||||
const outOfDate = it.version !== buildInfo.version;
|
||||
const warningCount = outOfDate ? 1 : 0;
|
||||
warnings += warningCount;
|
||||
if (warnings > 0) {
|
||||
@ -292,7 +292,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
|
||||
};
|
||||
|
||||
interface ProvisionerVersionPopoverProps {
|
||||
buildInfo?: BuildInfoResponse;
|
||||
buildInfo: BuildInfoResponse;
|
||||
provisioner: ProvisionerDaemon;
|
||||
}
|
||||
|
||||
@ -304,11 +304,9 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
|
||||
<Popover mode="hover">
|
||||
<PopoverTrigger>
|
||||
<span>
|
||||
{buildInfo
|
||||
? provisioner.version === buildInfo.version
|
||||
? "Up to date"
|
||||
: "Out of date"
|
||||
: provisioner.version}
|
||||
{provisioner.version === buildInfo.version
|
||||
? "Up to date"
|
||||
: "Out of date"}
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
@ -324,7 +322,7 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
|
||||
<p css={styles.text}>{provisioner.version}</p>
|
||||
<h4 css={styles.versionPopoverTitle}>Protocol version</h4>
|
||||
<p css={styles.text}>{provisioner.api_version}</p>
|
||||
{provisioner.api_version !== buildInfo?.provisioner_api_version && (
|
||||
{provisioner.api_version !== buildInfo.provisioner_api_version && (
|
||||
<p css={[styles.text, { fontSize: 13 }]}>
|
||||
This provisioner is out of date. You may experience issues when
|
||||
using a provisioner version that doesn’t match your Coder
|
||||
|
@ -3,15 +3,17 @@ import {
|
||||
organizationsPermissions,
|
||||
provisionerDaemonGroups,
|
||||
} from "api/queries/organizations";
|
||||
import type { Organization, ProvisionerDaemon } from "api/typesGenerated";
|
||||
import type { Organization } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Paywall } from "components/Paywall/Paywall";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import NotFoundPage from "pages/404Page/404Page";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { docs } from "utils/docs";
|
||||
import { useOrganizationSettings } from "./ManagementSettingsLayout";
|
||||
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
|
||||
|
||||
@ -20,6 +22,7 @@ const OrganizationProvisionersPage: FC = () => {
|
||||
organization: string;
|
||||
};
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const { entitlements } = useDashboard();
|
||||
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||
@ -27,42 +30,18 @@ const OrganizationProvisionersPage: FC = () => {
|
||||
const organization = organizations
|
||||
? getOrganizationByName(organizations, organizationName)
|
||||
: undefined;
|
||||
const permissionsQuery = useQuery(
|
||||
organizationsPermissions(organizations?.map((o) => o.id)),
|
||||
);
|
||||
const provisionersQuery = useQuery(provisionerDaemonGroups(organizationName));
|
||||
|
||||
if (!organization) {
|
||||
return <EmptyState message="Organization not found" />;
|
||||
}
|
||||
|
||||
if (permissionsQuery.isLoading || provisionersQuery.isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const permissions = permissionsQuery.data;
|
||||
const provisioners = provisionersQuery.data;
|
||||
const error = permissionsQuery.error || provisionersQuery.error;
|
||||
if (error || !permissions || !provisioners) {
|
||||
return <ErrorAlert error={error} />;
|
||||
}
|
||||
|
||||
// The user may not be able to edit this org but they can still see it because
|
||||
// they can edit members, etc. In this case they will be shown a read-only
|
||||
// summary page instead of the settings form.
|
||||
// Similarly, if the feature is not entitled then the user will not be able to
|
||||
// edit the organization.
|
||||
if (!permissions[organization.id]?.viewProvisioners) {
|
||||
// This probably doesn't work with the layout................fix this pls
|
||||
// Kayla, hey, yes you, you gotta fix this.
|
||||
// Don't scroll past this. It's important. Fix it!!!
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<OrganizationProvisionersPageView
|
||||
showPaywall={!entitlements.features.multiple_organizations.enabled}
|
||||
error={provisionersQuery.error}
|
||||
buildInfo={buildInfoQuery.data}
|
||||
provisioners={provisioners}
|
||||
provisioners={provisionersQuery.data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
MockProvisionerPskKey,
|
||||
MockProvisionerWithTags,
|
||||
MockUserProvisioner,
|
||||
mockApiError,
|
||||
} from "testHelpers/entities";
|
||||
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
|
||||
|
||||
@ -112,3 +113,18 @@ export const Empty: Story = {
|
||||
provisioners: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
error: mockApiError({
|
||||
message: "Fern is mad",
|
||||
detail: "Frieren slept in and didn't get groceries",
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export const Paywall: Story = {
|
||||
args: {
|
||||
showPaywall: true,
|
||||
},
|
||||
};
|
||||
|
@ -5,7 +5,10 @@ import type {
|
||||
ProvisionerKey,
|
||||
ProvisionerKeyDaemons,
|
||||
} from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Paywall } from "components/Paywall/Paywall";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { ProvisionerGroup } from "modules/provisioners/ProvisionerGroup";
|
||||
@ -13,16 +16,62 @@ import type { FC } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
|
||||
interface OrganizationProvisionersPageViewProps {
|
||||
/** Determines if the paywall will be shown or not */
|
||||
showPaywall?: boolean;
|
||||
|
||||
/** An error to display instead of the page content */
|
||||
error?: unknown;
|
||||
|
||||
/** Info about the version of coderd */
|
||||
buildInfo?: BuildInfoResponse;
|
||||
|
||||
/** Groups of provisioners, along with their key information */
|
||||
provisioners: readonly ProvisionerKeyDaemons[];
|
||||
provisioners?: readonly ProvisionerKeyDaemons[];
|
||||
}
|
||||
|
||||
export const OrganizationProvisionersPageView: FC<
|
||||
OrganizationProvisionersPageViewProps
|
||||
> = ({ buildInfo, provisioners }) => {
|
||||
> = ({ showPaywall, error, buildInfo, provisioners }) => {
|
||||
return (
|
||||
<div>
|
||||
<Stack
|
||||
alignItems="baseline"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader title="Provisioners" />
|
||||
{!showPaywall && (
|
||||
<Button
|
||||
endIcon={<OpenInNewIcon />}
|
||||
target="_blank"
|
||||
href={docs("/admin/provisioners")}
|
||||
>
|
||||
Create a provisioner
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
{showPaywall ? (
|
||||
<Paywall
|
||||
message="Provisioners"
|
||||
description="Provisioners run your Terraform to create templates and workspaces. You need a Premium license to use this feature for multiple organizations."
|
||||
documentationLink={docs("/")}
|
||||
/>
|
||||
) : error ? (
|
||||
<ErrorAlert error={error} />
|
||||
) : !buildInfo || !provisioners ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<ViewContent buildInfo={buildInfo} provisioners={provisioners} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type ViewContentProps = Required<
|
||||
Pick<OrganizationProvisionersPageViewProps, "buildInfo" | "provisioners">
|
||||
>;
|
||||
|
||||
const ViewContent: FC<ViewContentProps> = ({ buildInfo, provisioners }) => {
|
||||
const isEmpty = provisioners.every((group) => group.daemons.length === 0);
|
||||
|
||||
const provisionerGroupsCount = provisioners.length;
|
||||
@ -32,21 +81,7 @@ export const OrganizationProvisionersPageView: FC<
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Stack
|
||||
alignItems="baseline"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader title="Provisioners" />
|
||||
<Button
|
||||
endIcon={<OpenInNewIcon />}
|
||||
target="_blank"
|
||||
href={docs("/admin/provisioners")}
|
||||
>
|
||||
Create a provisioner
|
||||
</Button>
|
||||
</Stack>
|
||||
<>
|
||||
{isEmpty ? (
|
||||
<EmptyState
|
||||
message="No provisioners"
|
||||
@ -98,7 +133,7 @@ export const OrganizationProvisionersPageView: FC<
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user