mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
fix(site): standardize headers for Admin Settings page (#16911)
## Changes made - Switched almost all headers to use the `SettingHeader` component - Redesigned component to be more composition-based, to stay in line with the patterns we're starting to use more throughout the codebase - Refactored `SettingHeader` to be based on Radix and Tailwind, rather than Emotion/MUI - Added additional props to `SettingHeader` to help resolve issues with the component creating invalid HTML - Beefed up `SettingHeader` to have better out-of-the-box accessibility - Addressed some typographic problems in `SettingHeader` - Addressed some responsive layout problems for `SettingsHeader` - Added first-ever stories for `SettingsHeader` ## Notes - There are still a few headers that aren't using `SettingHeader` yet. There were some UI edge cases that meant I couldn't reliably bring it in without consulting the Design team first. I'm a little less worried about them, because they at least *look* like the other headers, but it'd be nice if we could centralize everything in a followup PR
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { docs } from "utils/docs";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "./SettingsHeader";
|
||||
|
||||
const meta: Meta<typeof SettingsHeader> = {
|
||||
title: "components/SettingsHeader",
|
||||
component: SettingsHeader,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsHeader>;
|
||||
|
||||
export const PrimaryHeaderOnly: Story = {
|
||||
args: {
|
||||
children: <SettingsHeaderTitle>This is a header</SettingsHeaderTitle>,
|
||||
},
|
||||
};
|
||||
|
||||
export const PrimaryHeaderWithDescription: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<SettingsHeaderTitle>Another primary header</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
This description can be any ReactNode. This provides more options for
|
||||
composition.
|
||||
</SettingsHeaderDescription>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const PrimaryHeaderWithDescriptionAndDocsLink: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<SettingsHeaderTitle>Another primary header</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
This description can be any ReactNode. This provides more options for
|
||||
composition.
|
||||
</SettingsHeaderDescription>
|
||||
</>
|
||||
),
|
||||
actions: <SettingsHeaderDocsLink href={docs("/admin/external-auth")} />,
|
||||
},
|
||||
};
|
||||
|
||||
export const SecondaryHeaderWithDescription: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<SettingsHeaderTitle level="h6" hierarchy="secondary">
|
||||
This is a secondary header.
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
The header's styling is completely independent of its semantics. Both
|
||||
can be adjusted independently to help avoid invalid HTML.
|
||||
</SettingsHeaderDescription>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const SecondaryHeaderWithDescriptionAndDocsLink: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<SettingsHeaderTitle level="h3" hierarchy="secondary">
|
||||
Another secondary header
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Nothing to add, really.
|
||||
</SettingsHeaderDescription>
|
||||
</>
|
||||
),
|
||||
actions: <SettingsHeaderDocsLink href={docs("/admin/external-auth")} />,
|
||||
},
|
||||
};
|
||||
@@ -1,74 +1,107 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { SquareArrowOutUpRightIcon } from "lucide-react";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import type { FC, PropsWithChildren, ReactNode } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
interface HeaderProps {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
secondary?: boolean;
|
||||
docsHref?: string;
|
||||
tooltip?: ReactNode;
|
||||
}
|
||||
|
||||
export const SettingsHeader: FC<HeaderProps> = ({
|
||||
title,
|
||||
description,
|
||||
docsHref,
|
||||
secondary,
|
||||
tooltip,
|
||||
type SettingsHeaderProps = Readonly<
|
||||
PropsWithChildren<{
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
}>
|
||||
>;
|
||||
export const SettingsHeader: FC<SettingsHeaderProps> = ({
|
||||
children,
|
||||
actions,
|
||||
className,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack alignItems="baseline" direction="row" justifyContent="space-between">
|
||||
<div css={{ maxWidth: 420, marginBottom: 24 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<h1
|
||||
css={[
|
||||
{
|
||||
fontSize: 32,
|
||||
fontWeight: 700,
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
lineHeight: "initial",
|
||||
margin: 0,
|
||||
marginBottom: 4,
|
||||
gap: 8,
|
||||
},
|
||||
secondary && {
|
||||
fontSize: 24,
|
||||
fontWeight: 500,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
{tooltip}
|
||||
</Stack>
|
||||
<hgroup className="flex flex-col justify-between items-start gap-2 pb-6 sm:flex-row">
|
||||
{/*
|
||||
* The text-sm class is only meant to adjust the font size of
|
||||
* SettingsDescription, but we need to apply it here. That way,
|
||||
* text-sm combines with the max-w-prose class and makes sure
|
||||
* we have a predictable max width for the header + description by
|
||||
* default.
|
||||
*/}
|
||||
<div className={cn("text-sm max-w-prose", className)}>{children}</div>
|
||||
{actions}
|
||||
</hgroup>
|
||||
);
|
||||
};
|
||||
|
||||
{description && (
|
||||
<span
|
||||
css={{
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "160%",
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
type SettingsHeaderDocsLinkProps = Readonly<
|
||||
PropsWithChildren<{ href: string }>
|
||||
>;
|
||||
export const SettingsHeaderDocsLink: FC<SettingsHeaderDocsLinkProps> = ({
|
||||
href,
|
||||
children = "Read the docs",
|
||||
}) => {
|
||||
return (
|
||||
<Button asChild variant="outline">
|
||||
<a href={href} target="_blank" rel="noreferrer">
|
||||
<SquareArrowOutUpRightIcon />
|
||||
{children}
|
||||
<span className="sr-only"> (link opens in new tab)</span>
|
||||
</a>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
{docsHref && (
|
||||
<Button asChild variant="outline">
|
||||
<a href={docsHref} target="_blank" rel="noreferrer">
|
||||
<SquareArrowOutUpRightIcon />
|
||||
Read the docs
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
const titleVariants = cva("m-0 pb-1 flex items-center gap-2 leading-tight", {
|
||||
variants: {
|
||||
hierarchy: {
|
||||
primary: "text-3xl font-bold",
|
||||
secondary: "text-2xl font-medium",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
hierarchy: "primary",
|
||||
},
|
||||
});
|
||||
type SettingsHeaderTitleProps = Readonly<
|
||||
PropsWithChildren<
|
||||
VariantProps<typeof titleVariants> & {
|
||||
level?: `h${1 | 2 | 3 | 4 | 5 | 6}`;
|
||||
tooltip?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
>
|
||||
>;
|
||||
export const SettingsHeaderTitle: FC<SettingsHeaderTitleProps> = ({
|
||||
children,
|
||||
tooltip,
|
||||
className,
|
||||
level = "h1",
|
||||
hierarchy = "primary",
|
||||
}) => {
|
||||
// Explicitly not using Radix's Slot component, because we don't want to
|
||||
// allow any arbitrary element to be composed into this. We specifically
|
||||
// only want to allow the six HTML headers. Anything else will likely result
|
||||
// in invalid markup
|
||||
const Title = level;
|
||||
return (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Title className={cn(titleVariants({ hierarchy }), className)}>
|
||||
{children}
|
||||
</Title>
|
||||
{tooltip}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type SettingsHeaderDescriptionProps = Readonly<
|
||||
PropsWithChildren<{
|
||||
className?: string;
|
||||
}>
|
||||
>;
|
||||
export const SettingsHeaderDescription: FC<SettingsHeaderDescriptionProps> = ({
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<p className={cn("m-0 text-content-secondary leading-relaxed", className)}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-5
@@ -8,7 +8,11 @@ import {
|
||||
} from "components/Badges/Badges";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { PopoverPaywall } from "components/Paywall/PopoverPaywall";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -54,10 +58,12 @@ export const AppearanceSettingsPageView: FC<
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader
|
||||
title="Appearance"
|
||||
description="Customize the look and feel of your Coder deployment."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Appearance</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Customize the look and feel of your Coder deployment.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Badges>
|
||||
<Popover mode="hover">
|
||||
|
||||
+14
-5
@@ -8,7 +8,12 @@ import TableRow from "@mui/material/TableRow";
|
||||
import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import type { FC } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
|
||||
@@ -22,10 +27,14 @@ export const ExternalAuthSettingsPageView: FC<
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader
|
||||
title="External Authentication"
|
||||
description="Coder integrates with GitHub, GitLab, BitBucket, Azure Repos, and OpenID Connect to authenticate developers with external services."
|
||||
docsHref={docs("/admin/external-auth")}
|
||||
/>
|
||||
actions={<SettingsHeaderDocsLink href={docs("/admin/external-auth")} />}
|
||||
>
|
||||
<SettingsHeaderTitle>External Authentication</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Coder integrates with GitHub, GitLab, BitBucket, Azure Repos, and
|
||||
OpenID Connect to authenticate developers with external services.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<video
|
||||
autoPlay
|
||||
|
||||
+12
-5
@@ -3,7 +3,11 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { FileUpload } from "components/FileUpload/FileUpload";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { ChevronLeftIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
@@ -50,10 +54,13 @@ export const AddNewLicensePageView: FC<AddNewLicenseProps> = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Add a license"
|
||||
description="Get access to high availability, RBAC, quotas, and more."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Add a license</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Get access to high availability, RBAC, quotas, and more.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Button asChild variant="outline">
|
||||
<RouterLink to="/deployment/licenses">
|
||||
<ChevronLeftIcon />
|
||||
|
||||
+11
-5
@@ -8,7 +8,11 @@ import Skeleton from "@mui/material/Skeleton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { GetLicensesResponse } from "api/api";
|
||||
import type { UserStatusChangeCount } from "api/typesGenerated";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useWindowSize } from "hooks/useWindowSize";
|
||||
import type { FC } from "react";
|
||||
@@ -60,10 +64,12 @@ const LicensesSettingsPageView: FC<Props> = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Licenses"
|
||||
description="Manage licenses to unlock Premium features."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Licenses</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Manage licenses to unlock Premium features.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button
|
||||
|
||||
+28
-10
@@ -1,6 +1,11 @@
|
||||
import type { SerpentOption } from "api/typesGenerated";
|
||||
import { Badges, DisabledBadge, EnabledBadge } from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
@@ -20,10 +25,14 @@ export const NetworkSettingsPageView: FC<NetworkSettingsPageViewProps> = ({
|
||||
<Stack direction="column" spacing={6}>
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="Network"
|
||||
description="Configure your deployment connectivity."
|
||||
docsHref={docs("/admin/networking")}
|
||||
/>
|
||||
actions={<SettingsHeaderDocsLink href={docs("/admin/networking")} />}
|
||||
>
|
||||
<SettingsHeaderTitle>Network</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Configure your deployment connectivity.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<OptionsTable
|
||||
options={options.filter((o) =>
|
||||
deploymentGroupHasParent(o.group, "Networking"),
|
||||
@@ -33,11 +42,20 @@ export const NetworkSettingsPageView: FC<NetworkSettingsPageViewProps> = ({
|
||||
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="Port Forwarding"
|
||||
secondary
|
||||
description="Port forwarding lets developers securely access processes on their Coder workspace from a local machine."
|
||||
docsHref={docs("/admin/networking/port-forwarding")}
|
||||
/>
|
||||
actions={
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/networking/port-forwarding")}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle level="h2" hierarchy="secondary">
|
||||
Port Forwarding
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Port forwarding lets developers securely access processes on their
|
||||
Coder workspace from a local machine.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Badges>
|
||||
{useDeploymentOptions(options, "Wildcard Access URL")[0].value !==
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
} from "api/queries/notifications";
|
||||
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
|
||||
import { useSearchParamsKey } from "hooks/useSearchParamsKey";
|
||||
import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider";
|
||||
@@ -43,22 +48,24 @@ export const NotificationsPage: FC = () => {
|
||||
<Helmet>
|
||||
<title>{pageTitle("Notifications Settings")}</title>
|
||||
</Helmet>
|
||||
|
||||
<SettingsHeader
|
||||
title={
|
||||
<>
|
||||
Notifications
|
||||
<span css={{ position: "relative", top: "-6px" }}>
|
||||
<FeatureStageBadge
|
||||
contentType={"beta"}
|
||||
size="lg"
|
||||
css={{ marginBottom: "5px", fontSize: "0.75rem" }}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
actions={
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/monitoring/notifications")}
|
||||
/>
|
||||
}
|
||||
description="Control delivery methods for notifications on this deployment."
|
||||
docsHref={docs("/admin/monitoring/notifications")}
|
||||
/>
|
||||
>
|
||||
<SettingsHeaderTitle
|
||||
tooltip={<FeatureStageBadge contentType="beta" size="lg" />}
|
||||
>
|
||||
Notifications
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Control delivery methods for notifications on this deployment.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Tabs active={tabState.value}>
|
||||
<TabsList>
|
||||
<TabLink to="?tab=events" value="events">
|
||||
|
||||
+12
-5
@@ -1,7 +1,11 @@
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { ChevronLeftIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
@@ -26,10 +30,13 @@ export const CreateOAuth2AppPageView: FC<CreateOAuth2AppProps> = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Add an OAuth2 application"
|
||||
description="Configure an application to use Coder as an OAuth2 provider."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Add an OAuth2 application</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Configure an application to use Coder as an OAuth2 provider.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Button variant="outline" asChild>
|
||||
<RouterLink to="/deployment/oauth2-provider/apps">
|
||||
<ChevronLeftIcon />
|
||||
|
||||
+12
-5
@@ -17,7 +17,11 @@ import { CopyableValue } from "components/CopyableValue/CopyableValue";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
import { ChevronLeftIcon } from "lucide-react";
|
||||
@@ -75,10 +79,13 @@ export const EditOAuth2AppPageView: FC<EditOAuth2AppProps> = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Edit OAuth2 application"
|
||||
description="Configure an application to use Coder as an OAuth2 provider."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Edit OAuth2 application</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Configure an application to use Coder as an OAuth2 provider.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Button variant="outline" asChild>
|
||||
<RouterLink to="/deployment/oauth2-provider/apps">
|
||||
<ChevronLeftIcon />
|
||||
|
||||
+11
-5
@@ -11,7 +11,11 @@ import TableRow from "@mui/material/TableRow";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||
@@ -37,10 +41,12 @@ const OAuth2AppsSettingsPageView: FC<OAuth2AppsSettingsProps> = ({
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="OAuth2 Applications"
|
||||
description="Configure applications to use Coder as an OAuth2 provider."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>OAuth2 Applications</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Configure applications to use Coder as an OAuth2 provider.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
|
||||
+31
-12
@@ -5,7 +5,12 @@ import {
|
||||
PremiumBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { PopoverPaywall } from "components/Paywall/PopoverPaywall";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Popover,
|
||||
@@ -30,13 +35,24 @@ export const ObservabilitySettingsPageView: FC<
|
||||
<>
|
||||
<Stack direction="column" spacing={6}>
|
||||
<div>
|
||||
<SettingsHeader title="Observability" />
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Observability</SettingsHeaderTitle>
|
||||
</SettingsHeader>
|
||||
|
||||
<SettingsHeader
|
||||
title="Audit Logging"
|
||||
secondary
|
||||
description="Allow auditors to monitor user operations in your deployment."
|
||||
docsHref={docs("/admin/security/audit-logs")}
|
||||
/>
|
||||
actions={
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/security/audit-logs")}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle hierarchy="secondary" level="h2">
|
||||
Audit Logging
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Allow auditors to monitor user operations in your deployment.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Badges>
|
||||
<Popover mode="hover">
|
||||
@@ -62,11 +78,14 @@ export const ObservabilitySettingsPageView: FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="Monitoring"
|
||||
secondary
|
||||
description="Monitoring your Coder application with logs and metrics."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle hierarchy="secondary" level="h2">
|
||||
Monitoring
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Monitoring your Coder application with logs and metrics.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<OptionsTable
|
||||
options={options.filter((o) =>
|
||||
|
||||
@@ -5,7 +5,12 @@ import type {
|
||||
SerpentOption,
|
||||
} from "api/typesGenerated";
|
||||
import { Link } from "components/Link/Link";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC } from "react";
|
||||
import { useDeploymentOptions } from "utils/deployOptions";
|
||||
@@ -30,10 +35,14 @@ export const OverviewPageView: FC<OverviewPageViewProps> = ({
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader
|
||||
title="General"
|
||||
description="Information about your Coder deployment."
|
||||
docsHref={docs("/admin/setup")}
|
||||
/>
|
||||
actions={<SettingsHeaderDocsLink href={docs("/admin/setup")} />}
|
||||
>
|
||||
<SettingsHeaderTitle>General</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Information about your Coder deployment.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Stack spacing={4}>
|
||||
<UserEngagementChart
|
||||
data={dailyActiveUsers?.entries.map((i) => ({
|
||||
|
||||
+34
-15
@@ -5,7 +5,12 @@ import {
|
||||
EnabledBadge,
|
||||
PremiumBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
@@ -31,10 +36,12 @@ export const SecuritySettingsPageView: FC<SecuritySettingsPageViewProps> = ({
|
||||
return (
|
||||
<Stack direction="column" spacing={6}>
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="Security"
|
||||
description="Ensure your Coder deployment is secure."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Security</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Ensure your Coder deployment is secure.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<OptionsTable
|
||||
options={useDeploymentOptions(
|
||||
@@ -48,11 +55,20 @@ export const SecuritySettingsPageView: FC<SecuritySettingsPageViewProps> = ({
|
||||
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="Browser Only Connections"
|
||||
secondary
|
||||
description="Block all workspace access via SSH, port forward, and other non-browser connections."
|
||||
docsHref={docs("/admin/networking#browser-only-connections")}
|
||||
/>
|
||||
actions={
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/networking#browser-only-connections")}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle level="h2" hierarchy="secondary">
|
||||
Browser-Only Connections
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Block all workspace access via SSH, port forward, and other
|
||||
non-browser connections.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Badges>
|
||||
{featureBrowserOnlyEnabled ? <EnabledBadge /> : <DisabledBadge />}
|
||||
@@ -62,11 +78,14 @@ export const SecuritySettingsPageView: FC<SecuritySettingsPageViewProps> = ({
|
||||
|
||||
{tlsOptions.length > 0 && (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="TLS"
|
||||
secondary
|
||||
description="Ensure TLS is properly configured for your Coder deployment."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle level="h2" hierarchy="secondary">
|
||||
TLS
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Ensure TLS is properly configured for your Coder deployment.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<OptionsTable options={tlsOptions} />
|
||||
</div>
|
||||
|
||||
+33
-12
@@ -1,6 +1,11 @@
|
||||
import type { SerpentOption } from "api/typesGenerated";
|
||||
import { Badges, DisabledBadge, EnabledBadge } from "components/Badges/Badges";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderDocsLink,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
deploymentGroupHasParent,
|
||||
@@ -27,14 +32,24 @@ export const UserAuthSettingsPageView = ({
|
||||
<>
|
||||
<Stack direction="column" spacing={6}>
|
||||
<div>
|
||||
<SettingsHeader title="User Authentication" />
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>User Authentication</SettingsHeaderTitle>
|
||||
</SettingsHeader>
|
||||
|
||||
<SettingsHeader
|
||||
title="Login with OpenID Connect"
|
||||
secondary
|
||||
description="Set up authentication to login with OpenID Connect."
|
||||
docsHref={docs("/admin/users/oidc-auth#openid-connect")}
|
||||
/>
|
||||
actions={
|
||||
<SettingsHeaderDocsLink
|
||||
href={docs("/admin/users/oidc-auth#openid-connect")}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle level="h2" hierarchy="secondary">
|
||||
Login with OpenID Connect
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Set up authentication to login with OpenID Connect.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Badges>{oidcEnabled ? <EnabledBadge /> : <DisabledBadge />}</Badges>
|
||||
|
||||
@@ -49,11 +64,17 @@ export const UserAuthSettingsPageView = ({
|
||||
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="Login with GitHub"
|
||||
secondary
|
||||
description="Set up authentication to login with GitHub."
|
||||
docsHref={docs("/admin/users/github-auth")}
|
||||
/>
|
||||
actions={
|
||||
<SettingsHeaderDocsLink href={docs("/admin/users/github-auth")} />
|
||||
}
|
||||
>
|
||||
<SettingsHeaderTitle level="h2" hierarchy="secondary">
|
||||
Login with GitHub
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Set up authentication to login with GitHub.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Badges>
|
||||
{githubEnabled ? <EnabledBadge /> : <DisabledBadge />}
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
HorizontalForm,
|
||||
} from "components/Form/Form";
|
||||
import { IconField } from "components/IconField/IconField";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import { useFormik } from "formik";
|
||||
import type { FC } from "react";
|
||||
@@ -53,10 +57,12 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader
|
||||
title="New Group"
|
||||
description="Create a group in this organization."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>New Group</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Create a group in this organization.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<HorizontalForm onSubmit={form.handleSubmit}>
|
||||
<FormSection
|
||||
|
||||
@@ -32,7 +32,11 @@ import {
|
||||
MoreMenuTrigger,
|
||||
ThreeDotsButton,
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Table,
|
||||
@@ -106,10 +110,15 @@ export const GroupPage: FC = () => {
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title={groupData?.display_name || groupData?.name}
|
||||
description="Manage members for this group."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>
|
||||
{groupData?.display_name || groupData?.name || "Unknown Group"}
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Manage members for this group.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
{canUpdateGroup && (
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button
|
||||
|
||||
@@ -6,7 +6,11 @@ import { Button } from "components/Button/Button";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||
@@ -80,10 +84,14 @@ export const GroupsPage: FC = () => {
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Groups"
|
||||
description={`Manage groups for this ${showOrganizations ? "organization" : "deployment"}.`}
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Groups</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Manage groups for this{" "}
|
||||
{showOrganizations ? "organization" : "deployment"}.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
{groupsEnabled && permissions.createGroup && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
|
||||
@@ -154,12 +154,12 @@ export const WorkspaceProxyPage: FC = () => {
|
||||
<span>OK</span>
|
||||
) : (
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
{[...errors, ...warnings].map((msg, i) => (
|
||||
{[...errors, ...warnings].map((msg) => (
|
||||
<span
|
||||
key={msg}
|
||||
css={{
|
||||
":first-letter": { textTransform: "uppercase" },
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
{msg}
|
||||
</span>
|
||||
|
||||
@@ -24,7 +24,11 @@ import type {
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { FormFields, FormFooter, VerticalForm } from "components/Form/Form";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useFormik } from "formik";
|
||||
@@ -78,10 +82,15 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title={`${role ? "Edit" : "Create"} Custom Role`}
|
||||
description="Set a name and permissions for this role."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>
|
||||
{role ? "Edit" : "Create"} Custom Role
|
||||
</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Set a name and permissions for this role.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<div className="flex space-x-2 items-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -4,7 +4,11 @@ import type { Role } from "api/typesGenerated";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
@@ -71,10 +75,12 @@ export const CustomRolesPage: FC = () => {
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Roles"
|
||||
description="Manage roles for this organization."
|
||||
/>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Roles</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Manage roles for this organization.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
</Stack>
|
||||
|
||||
<CustomRolesPageView
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
ThreeDotsButton,
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
import { PaginationContainer } from "components/PaginationWidget/PaginationContainer";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Table,
|
||||
@@ -79,7 +82,10 @@ export const OrganizationMembersPageView: FC<
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title="Members" />
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Members</SettingsHeaderTitle>
|
||||
</SettingsHeader>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
{Boolean(error) && <ErrorAlert error={error} />}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ 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 {
|
||||
SettingsHeader,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { ProvisionerGroup } from "modules/provisioners/ProvisionerGroup";
|
||||
import type { FC } from "react";
|
||||
@@ -39,7 +42,10 @@ export const OrganizationProvisionersPageView: FC<
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<SettingsHeader title="Provisioners" />
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Provisioners</SettingsHeaderTitle>
|
||||
</SettingsHeader>
|
||||
|
||||
{!showPaywall && (
|
||||
<Button
|
||||
endIcon={<OpenInNewIcon />}
|
||||
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
HorizontalForm,
|
||||
} from "components/Form/Form";
|
||||
import { IconField } from "components/IconField/IconField";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import { useFormik } from "formik";
|
||||
import { type FC, useState } from "react";
|
||||
@@ -66,7 +69,10 @@ export const OrganizationSettingsPageView: FC<
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title="Settings" />
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Settings</SettingsHeaderTitle>
|
||||
</SettingsHeader>
|
||||
|
||||
{Boolean(error) && !isApiValidationError(error) && (
|
||||
<div css={{ marginBottom: 32 }}>
|
||||
<ErrorAlert error={error} />
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { useProxy } from "contexts/ProxyContext";
|
||||
import type { FC } from "react";
|
||||
import { Section } from "../Section";
|
||||
import { WorkspaceProxyView } from "./WorkspaceProxyView";
|
||||
|
||||
export const WorkspaceProxyPage: FC = () => {
|
||||
const description =
|
||||
"Workspace proxies improve terminal and web app connections to workspaces.";
|
||||
|
||||
const {
|
||||
proxyLatencies,
|
||||
proxies,
|
||||
@@ -17,29 +13,14 @@ export const WorkspaceProxyPage: FC = () => {
|
||||
} = useProxy();
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Workspace Proxies"
|
||||
css={(theme) => ({
|
||||
"& code": {
|
||||
background: theme.palette.divider,
|
||||
fontSize: 12,
|
||||
padding: "2px 4px",
|
||||
color: theme.palette.text.primary,
|
||||
borderRadius: 2,
|
||||
},
|
||||
})}
|
||||
description={description}
|
||||
layout="fluid"
|
||||
>
|
||||
<WorkspaceProxyView
|
||||
proxyLatencies={proxyLatencies}
|
||||
proxies={proxies}
|
||||
isLoading={proxiesLoading}
|
||||
hasLoaded={proxiesFetched}
|
||||
getWorkspaceProxiesError={proxiesError}
|
||||
preferredProxy={proxy.proxy}
|
||||
/>
|
||||
</Section>
|
||||
<WorkspaceProxyView
|
||||
proxyLatencies={proxyLatencies}
|
||||
proxies={proxies}
|
||||
isLoading={proxiesLoading}
|
||||
hasLoaded={proxiesFetched}
|
||||
getWorkspaceProxiesError={proxiesError}
|
||||
preferredProxy={proxy.proxy}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@ import TableRow from "@mui/material/TableRow";
|
||||
import type { Region } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
@@ -34,6 +39,14 @@ export const WorkspaceProxyView: FC<WorkspaceProxyViewProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<Stack>
|
||||
<SettingsHeader>
|
||||
<SettingsHeaderTitle>Workspace Proxies</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Workspace proxies improve terminal and web app connections to
|
||||
workspaces.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
{Boolean(getWorkspaceProxiesError) && (
|
||||
<ErrorAlert error={getWorkspaceProxiesError} />
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,11 @@ import {
|
||||
PaginationContainer,
|
||||
type PaginationResult,
|
||||
} from "components/PaginationWidget/PaginationContainer";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import {
|
||||
SettingsHeader,
|
||||
SettingsHeaderDescription,
|
||||
SettingsHeaderTitle,
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserPlusIcon } from "lucide-react";
|
||||
import type { ComponentProps, FC } from "react";
|
||||
@@ -68,24 +72,23 @@ export const UsersPageView: FC<UsersPageViewProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
alignItems="baseline"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
<SettingsHeader
|
||||
actions={
|
||||
canCreateUser && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
<UserPlusIcon />
|
||||
Create user
|
||||
</RouterLink>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<SettingsHeader
|
||||
title="Users"
|
||||
description="Manage user accounts and permissions."
|
||||
/>
|
||||
{canCreateUser && (
|
||||
<Button asChild>
|
||||
<RouterLink to="create">
|
||||
<UserPlusIcon />
|
||||
Create user
|
||||
</RouterLink>
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
<SettingsHeaderTitle>Users</SettingsHeaderTitle>
|
||||
<SettingsHeaderDescription>
|
||||
Manage user accounts and permissions.
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<UsersFilter {...filterProps} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user