mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: Add deployment settings page (#4590)
* Add base components for the Settings Page * WIP OIDC page * Imrove layout * Add table * Abstract option * Refactor badges * Load settings from the API * Update deployment page * feat: Add deployment settings page This allows deployment admins to view options set on their deployments. * Format * Remove replicas table since it's not used * Remove references to HA table * Fix tests * Improve language Co-authored-by: Bruno Quaresma <bruno@coder.com>
This commit is contained in:
@ -38,7 +38,7 @@ type DeploymentFlags struct {
|
|||||||
OAuth2GithubEnterpriseBaseURL *StringFlag `json:"oauth2_github_enterprise_base_url" typescript:",notnull"`
|
OAuth2GithubEnterpriseBaseURL *StringFlag `json:"oauth2_github_enterprise_base_url" typescript:",notnull"`
|
||||||
OIDCAllowSignups *BoolFlag `json:"oidc_allow_signups" typescript:",notnull"`
|
OIDCAllowSignups *BoolFlag `json:"oidc_allow_signups" typescript:",notnull"`
|
||||||
OIDCClientID *StringFlag `json:"oidc_client_id" typescript:",notnull"`
|
OIDCClientID *StringFlag `json:"oidc_client_id" typescript:",notnull"`
|
||||||
OIDCClientSecret *StringFlag `json:"oidc_cliet_secret" typescript:",notnull"`
|
OIDCClientSecret *StringFlag `json:"oidc_client_secret" typescript:",notnull"`
|
||||||
OIDCEmailDomain *StringFlag `json:"oidc_email_domain" typescript:",notnull"`
|
OIDCEmailDomain *StringFlag `json:"oidc_email_domain" typescript:",notnull"`
|
||||||
OIDCIssuerURL *StringFlag `json:"oidc_issuer_url" typescript:",notnull"`
|
OIDCIssuerURL *StringFlag `json:"oidc_issuer_url" typescript:",notnull"`
|
||||||
OIDCScopes *StringArrayFlag `json:"oidc_scopes" typescript:",notnull"`
|
OIDCScopes *StringArrayFlag `json:"oidc_scopes" typescript:",notnull"`
|
||||||
@ -49,7 +49,7 @@ type DeploymentFlags struct {
|
|||||||
TLSCertFiles *StringArrayFlag `json:"tls_cert_files" typescript:",notnull"`
|
TLSCertFiles *StringArrayFlag `json:"tls_cert_files" typescript:",notnull"`
|
||||||
TLSClientCAFile *StringFlag `json:"tls_client_ca_file" typescript:",notnull"`
|
TLSClientCAFile *StringFlag `json:"tls_client_ca_file" typescript:",notnull"`
|
||||||
TLSClientAuth *StringFlag `json:"tls_client_auth" typescript:",notnull"`
|
TLSClientAuth *StringFlag `json:"tls_client_auth" typescript:",notnull"`
|
||||||
TLSKeyFiles *StringArrayFlag `json:"tls_key_tiles" typescript:",notnull"`
|
TLSKeyFiles *StringArrayFlag `json:"tls_key_files" typescript:",notnull"`
|
||||||
TLSMinVersion *StringFlag `json:"tls_min_version" typescript:",notnull"`
|
TLSMinVersion *StringFlag `json:"tls_min_version" typescript:",notnull"`
|
||||||
TraceEnable *BoolFlag `json:"trace_enable" typescript:",notnull"`
|
TraceEnable *BoolFlag `json:"trace_enable" typescript:",notnull"`
|
||||||
SecureAuthCookie *BoolFlag `json:"secure_auth_cookie" typescript:",notnull"`
|
SecureAuthCookie *BoolFlag `json:"secure_auth_cookie" typescript:",notnull"`
|
||||||
|
@ -21,6 +21,7 @@ import { XServiceContext } from "xServices/StateContext"
|
|||||||
import { AuthAndFrame } from "./components/AuthAndFrame/AuthAndFrame"
|
import { AuthAndFrame } from "./components/AuthAndFrame/AuthAndFrame"
|
||||||
import { RequireAuth } from "./components/RequireAuth/RequireAuth"
|
import { RequireAuth } from "./components/RequireAuth/RequireAuth"
|
||||||
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
|
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
|
||||||
|
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"
|
||||||
|
|
||||||
// Lazy load pages
|
// Lazy load pages
|
||||||
// - Pages that are secondary, not in the main navigation or not usually accessed
|
// - Pages that are secondary, not in the main navigation or not usually accessed
|
||||||
@ -67,6 +68,18 @@ const GroupPage = lazy(() => import("./pages/GroupsPage/GroupPage"))
|
|||||||
const SettingsGroupPage = lazy(
|
const SettingsGroupPage = lazy(
|
||||||
() => import("./pages/GroupsPage/SettingsGroupPage"),
|
() => import("./pages/GroupsPage/SettingsGroupPage"),
|
||||||
)
|
)
|
||||||
|
const GeneralSettingsPage = lazy(
|
||||||
|
() => import("./pages/DeploySettingsPage/GeneralSettingsPage"),
|
||||||
|
)
|
||||||
|
const SecuritySettingsPage = lazy(
|
||||||
|
() => import("./pages/DeploySettingsPage/SecuritySettingsPage"),
|
||||||
|
)
|
||||||
|
const AuthSettingsPage = lazy(
|
||||||
|
() => import("./pages/DeploySettingsPage/AuthSettingsPage"),
|
||||||
|
)
|
||||||
|
const NetworkSettingsPage = lazy(
|
||||||
|
() => import("./pages/DeploySettingsPage/NetworkSettingsPage"),
|
||||||
|
)
|
||||||
|
|
||||||
export const AppRouter: FC = () => {
|
export const AppRouter: FC = () => {
|
||||||
const xServices = useContext(XServiceContext)
|
const xServices = useContext(XServiceContext)
|
||||||
@ -237,6 +250,65 @@ export const AppRouter: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/settings/deployment">
|
||||||
|
<Route
|
||||||
|
path="general"
|
||||||
|
element={
|
||||||
|
<AuthAndFrame>
|
||||||
|
<RequirePermission
|
||||||
|
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
|
||||||
|
>
|
||||||
|
<DeploySettingsLayout>
|
||||||
|
<GeneralSettingsPage />
|
||||||
|
</DeploySettingsLayout>
|
||||||
|
</RequirePermission>
|
||||||
|
</AuthAndFrame>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="security"
|
||||||
|
element={
|
||||||
|
<AuthAndFrame>
|
||||||
|
<RequirePermission
|
||||||
|
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
|
||||||
|
>
|
||||||
|
<DeploySettingsLayout>
|
||||||
|
<SecuritySettingsPage />
|
||||||
|
</DeploySettingsLayout>
|
||||||
|
</RequirePermission>
|
||||||
|
</AuthAndFrame>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="network"
|
||||||
|
element={
|
||||||
|
<AuthAndFrame>
|
||||||
|
<RequirePermission
|
||||||
|
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
|
||||||
|
>
|
||||||
|
<DeploySettingsLayout>
|
||||||
|
<NetworkSettingsPage />
|
||||||
|
</DeploySettingsLayout>
|
||||||
|
</RequirePermission>
|
||||||
|
</AuthAndFrame>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="auth"
|
||||||
|
element={
|
||||||
|
<AuthAndFrame>
|
||||||
|
<RequirePermission
|
||||||
|
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
|
||||||
|
>
|
||||||
|
<DeploySettingsLayout>
|
||||||
|
<AuthSettingsPage />
|
||||||
|
</DeploySettingsLayout>
|
||||||
|
</RequirePermission>
|
||||||
|
</AuthAndFrame>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="settings" element={<SettingsLayout />}>
|
<Route path="settings" element={<SettingsLayout />}>
|
||||||
<Route path="account" element={<AccountPage />} />
|
<Route path="account" element={<AccountPage />} />
|
||||||
<Route path="security" element={<SecurityPage />} />
|
<Route path="security" element={<SecurityPage />} />
|
||||||
|
@ -641,3 +641,14 @@ export const getAgentListeningPorts = async (
|
|||||||
)
|
)
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getDeploymentFlags =
|
||||||
|
async (): Promise<TypesGen.DeploymentFlags> => {
|
||||||
|
const response = await axios.get(`/api/v2/flags/deployment`)
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getReplicas = async (): Promise<TypesGen.Replica[]> => {
|
||||||
|
const response = await axios.get(`/api/v2/replicas`)
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
@ -293,7 +293,7 @@ export interface DeploymentFlags {
|
|||||||
readonly oauth2_github_enterprise_base_url: StringFlag
|
readonly oauth2_github_enterprise_base_url: StringFlag
|
||||||
readonly oidc_allow_signups: BoolFlag
|
readonly oidc_allow_signups: BoolFlag
|
||||||
readonly oidc_client_id: StringFlag
|
readonly oidc_client_id: StringFlag
|
||||||
readonly oidc_cliet_secret: StringFlag
|
readonly oidc_client_secret: StringFlag
|
||||||
readonly oidc_email_domain: StringFlag
|
readonly oidc_email_domain: StringFlag
|
||||||
readonly oidc_issuer_url: StringFlag
|
readonly oidc_issuer_url: StringFlag
|
||||||
readonly oidc_scopes: StringArrayFlag
|
readonly oidc_scopes: StringArrayFlag
|
||||||
@ -304,7 +304,7 @@ export interface DeploymentFlags {
|
|||||||
readonly tls_cert_files: StringArrayFlag
|
readonly tls_cert_files: StringArrayFlag
|
||||||
readonly tls_client_ca_file: StringFlag
|
readonly tls_client_ca_file: StringFlag
|
||||||
readonly tls_client_auth: StringFlag
|
readonly tls_client_auth: StringFlag
|
||||||
readonly tls_key_tiles: StringArrayFlag
|
readonly tls_key_files: StringArrayFlag
|
||||||
readonly tls_min_version: StringFlag
|
readonly tls_min_version: StringFlag
|
||||||
readonly trace_enable: BoolFlag
|
readonly trace_enable: BoolFlag
|
||||||
readonly secure_auth_cookie: BoolFlag
|
readonly secure_auth_cookie: BoolFlag
|
||||||
|
79
site/src/components/DeploySettingsLayout/Badges.tsx
Normal file
79
site/src/components/DeploySettingsLayout/Badges.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import React, { PropsWithChildren } from "react"
|
||||||
|
import { combineClasses } from "util/combineClasses"
|
||||||
|
|
||||||
|
export const EnabledBadge: React.FC = () => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return (
|
||||||
|
<span className={combineClasses([styles.badge, styles.enabledBadge])}>
|
||||||
|
Enabled
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DisabledBadge: React.FC = () => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return (
|
||||||
|
<span className={combineClasses([styles.badge, styles.disabledBadge])}>
|
||||||
|
Disabled
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnterpriseBadge: React.FC = () => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return (
|
||||||
|
<span className={combineClasses([styles.badge, styles.enterpriseBadge])}>
|
||||||
|
Enterprise
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Badges: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
className={styles.badges}
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={1}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
badges: {
|
||||||
|
margin: theme.spacing(0, 0, 2),
|
||||||
|
},
|
||||||
|
|
||||||
|
badge: {
|
||||||
|
fontSize: 10,
|
||||||
|
height: 24,
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: "0.085em",
|
||||||
|
padding: theme.spacing(0, 1.5),
|
||||||
|
borderRadius: 9999,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
width: "fit-content",
|
||||||
|
},
|
||||||
|
|
||||||
|
enterpriseBadge: {
|
||||||
|
backgroundColor: theme.palette.info.dark,
|
||||||
|
border: `1px solid ${theme.palette.info.light}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
enabledBadge: {
|
||||||
|
border: `1px solid ${theme.palette.success.light}`,
|
||||||
|
backgroundColor: theme.palette.success.dark,
|
||||||
|
},
|
||||||
|
|
||||||
|
disabledBadge: {
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}))
|
@ -0,0 +1,73 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import { Margins } from "components/Margins/Margins"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import { Sidebar } from "./Sidebar"
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
PropsWithChildren,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
} from "react"
|
||||||
|
import { useActor } from "@xstate/react"
|
||||||
|
import { XServiceContext } from "xServices/StateContext"
|
||||||
|
import { Loader } from "components/Loader/Loader"
|
||||||
|
import { DeploymentFlags } from "api/typesGenerated"
|
||||||
|
|
||||||
|
type DeploySettingsContextValue = { deploymentFlags: DeploymentFlags }
|
||||||
|
|
||||||
|
const DeploySettingsContext = createContext<
|
||||||
|
DeploySettingsContextValue | undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
|
export const useDeploySettings = (): DeploySettingsContextValue => {
|
||||||
|
const context = useContext(DeploySettingsContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useDeploySettings should be used inside of DeploySettingsLayout",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeploySettingsLayout: React.FC<PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const xServices = useContext(XServiceContext)
|
||||||
|
const [state, send] = useActor(xServices.deploymentFlagsXService)
|
||||||
|
const styles = useStyles()
|
||||||
|
const { deploymentFlags } = state.context
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state.matches("idle")) {
|
||||||
|
send("LOAD")
|
||||||
|
}
|
||||||
|
}, [send, state])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Margins>
|
||||||
|
<Stack className={styles.wrapper} direction="row" spacing={5}>
|
||||||
|
<Sidebar />
|
||||||
|
<main className={styles.content}>
|
||||||
|
{deploymentFlags ? (
|
||||||
|
<DeploySettingsContext.Provider value={{ deploymentFlags }}>
|
||||||
|
{children}
|
||||||
|
</DeploySettingsContext.Provider>
|
||||||
|
) : (
|
||||||
|
<Loader />
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
</Stack>
|
||||||
|
</Margins>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
wrapper: {
|
||||||
|
padding: theme.spacing(6, 0),
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
maxWidth: 800,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
}))
|
67
site/src/components/DeploySettingsLayout/Header.tsx
Normal file
67
site/src/components/DeploySettingsLayout/Header.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import Button from "@material-ui/core/Button"
|
||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import LaunchOutlined from "@material-ui/icons/LaunchOutlined"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const Header: React.FC<{
|
||||||
|
title: string | JSX.Element
|
||||||
|
description: string | JSX.Element
|
||||||
|
secondary?: boolean
|
||||||
|
docsHref?: string
|
||||||
|
}> = ({ title, description, docsHref, secondary }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack alignItems="baseline" direction="row" justifyContent="space-between">
|
||||||
|
<div className={styles.headingGroup}>
|
||||||
|
<h1 className={`${styles.title} ${secondary ? "secondary" : ""}`}>
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
<span className={styles.description}>{description}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{docsHref && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
startIcon={<LaunchOutlined />}
|
||||||
|
component="a"
|
||||||
|
href={docsHref}
|
||||||
|
target="_blank"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
Read the docs
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
headingGroup: {
|
||||||
|
maxWidth: 420,
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
|
||||||
|
title: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 700,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
lineHeight: "initial",
|
||||||
|
margin: 0,
|
||||||
|
marginBottom: theme.spacing(0.5),
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
|
||||||
|
"&.secondary": {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
description: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
lineHeight: "160%",
|
||||||
|
},
|
||||||
|
}))
|
40
site/src/components/DeploySettingsLayout/Option.tsx
Normal file
40
site/src/components/DeploySettingsLayout/Option.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import React, { PropsWithChildren } from "react"
|
||||||
|
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
|
||||||
|
|
||||||
|
export const OptionName: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return <span className={styles.optionName}>{children}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptionDescription: React.FC<PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return <span className={styles.optionDescription}>{children}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptionValue: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return <span className={styles.optionValue}>{children}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
optionName: {
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
optionDescription: {
|
||||||
|
display: "block",
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: theme.spacing(0.5),
|
||||||
|
},
|
||||||
|
optionValue: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||||
|
|
||||||
|
"& ul": {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
114
site/src/components/DeploySettingsLayout/Sidebar.tsx
Normal file
114
site/src/components/DeploySettingsLayout/Sidebar.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import LaunchOutlined from "@material-ui/icons/LaunchOutlined"
|
||||||
|
import LockRounded from "@material-ui/icons/LockRounded"
|
||||||
|
import Globe from "@material-ui/icons/Public"
|
||||||
|
import VpnKeyOutlined from "@material-ui/icons/VpnKeyOutlined"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import React, { ElementType, PropsWithChildren, ReactNode } from "react"
|
||||||
|
import { NavLink } from "react-router-dom"
|
||||||
|
import { combineClasses } from "util/combineClasses"
|
||||||
|
|
||||||
|
const SidebarNavItem: React.FC<
|
||||||
|
PropsWithChildren<{ href: string; icon: ReactNode }>
|
||||||
|
> = ({ children, href, icon }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={href}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
combineClasses([
|
||||||
|
styles.sidebarNavItem,
|
||||||
|
isActive ? styles.sidebarNavItemActive : undefined,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack alignItems="center" spacing={1.5} direction="row">
|
||||||
|
{icon}
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
|
||||||
|
icon: Icon,
|
||||||
|
}) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return <Icon className={styles.sidebarNavItemIcon} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Sidebar: React.FC = () => {
|
||||||
|
const styles = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={styles.sidebar}>
|
||||||
|
<SidebarNavItem
|
||||||
|
href="../general"
|
||||||
|
icon={<SidebarNavItemIcon icon={LaunchOutlined} />}
|
||||||
|
>
|
||||||
|
General
|
||||||
|
</SidebarNavItem>
|
||||||
|
<SidebarNavItem
|
||||||
|
href="../auth"
|
||||||
|
icon={<SidebarNavItemIcon icon={VpnKeyOutlined} />}
|
||||||
|
>
|
||||||
|
Authentication
|
||||||
|
</SidebarNavItem>
|
||||||
|
<SidebarNavItem
|
||||||
|
href="../network"
|
||||||
|
icon={<SidebarNavItemIcon icon={Globe} />}
|
||||||
|
>
|
||||||
|
Network
|
||||||
|
</SidebarNavItem>
|
||||||
|
<SidebarNavItem
|
||||||
|
href="../security"
|
||||||
|
icon={<SidebarNavItemIcon icon={LockRounded} />}
|
||||||
|
>
|
||||||
|
Security
|
||||||
|
</SidebarNavItem>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
sidebar: {
|
||||||
|
width: 245,
|
||||||
|
},
|
||||||
|
|
||||||
|
sidebarNavItem: {
|
||||||
|
color: "inherit",
|
||||||
|
display: "block",
|
||||||
|
fontSize: 16,
|
||||||
|
textDecoration: "none",
|
||||||
|
padding: theme.spacing(1.5, 1.5, 1.5, 3),
|
||||||
|
borderRadius: theme.shape.borderRadius / 2,
|
||||||
|
transition: "background-color 0.15s ease-in-out",
|
||||||
|
marginBottom: 1,
|
||||||
|
position: "relative",
|
||||||
|
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
sidebarNavItemActive: {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
|
||||||
|
"&:before": {
|
||||||
|
content: '""',
|
||||||
|
display: "block",
|
||||||
|
width: 3,
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
backgroundColor: theme.palette.secondary.dark,
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
sidebarNavItemIcon: {
|
||||||
|
width: theme.spacing(2),
|
||||||
|
height: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}))
|
@ -17,6 +17,7 @@ export const Navbar: React.FC = () => {
|
|||||||
const canViewAuditLog =
|
const canViewAuditLog =
|
||||||
featureVisibility[FeatureNames.AuditLog] &&
|
featureVisibility[FeatureNames.AuditLog] &&
|
||||||
Boolean(permissions?.viewAuditLog)
|
Boolean(permissions?.viewAuditLog)
|
||||||
|
const canViewDeployment = Boolean(permissions?.viewDeploymentFlags)
|
||||||
const onSignOut = () => authSend("SIGN_OUT")
|
const onSignOut = () => authSend("SIGN_OUT")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -24,6 +25,7 @@ export const Navbar: React.FC = () => {
|
|||||||
user={me}
|
user={me}
|
||||||
onSignOut={onSignOut}
|
onSignOut={onSignOut}
|
||||||
canViewAuditLog={canViewAuditLog}
|
canViewAuditLog={canViewAuditLog}
|
||||||
|
canViewDeployment={canViewDeployment}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -22,26 +22,54 @@ describe("NavbarView", () => {
|
|||||||
|
|
||||||
it("renders content", async () => {
|
it("renders content", async () => {
|
||||||
// When
|
// When
|
||||||
render(<NavbarView user={MockUser} onSignOut={noop} canViewAuditLog />)
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
await screen.findAllByText("Coder", { exact: false })
|
await screen.findAllByText("Coder", { exact: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("workspaces nav link has the correct href", async () => {
|
it("workspaces nav link has the correct href", async () => {
|
||||||
render(<NavbarView user={MockUser} onSignOut={noop} canViewAuditLog />)
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
const workspacesLink = await screen.findByText(navLanguage.workspaces)
|
const workspacesLink = await screen.findByText(navLanguage.workspaces)
|
||||||
expect((workspacesLink as HTMLAnchorElement).href).toContain("/workspaces")
|
expect((workspacesLink as HTMLAnchorElement).href).toContain("/workspaces")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("templates nav link has the correct href", async () => {
|
it("templates nav link has the correct href", async () => {
|
||||||
render(<NavbarView user={MockUser} onSignOut={noop} canViewAuditLog />)
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
const templatesLink = await screen.findByText(navLanguage.templates)
|
const templatesLink = await screen.findByText(navLanguage.templates)
|
||||||
expect((templatesLink as HTMLAnchorElement).href).toContain("/templates")
|
expect((templatesLink as HTMLAnchorElement).href).toContain("/templates")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("users nav link has the correct href", async () => {
|
it("users nav link has the correct href", async () => {
|
||||||
render(<NavbarView user={MockUser} onSignOut={noop} canViewAuditLog />)
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
const userLink = await screen.findByText(navLanguage.users)
|
const userLink = await screen.findByText(navLanguage.users)
|
||||||
expect((userLink as HTMLAnchorElement).href).toContain("/users")
|
expect((userLink as HTMLAnchorElement).href).toContain("/users")
|
||||||
})
|
})
|
||||||
@ -55,7 +83,14 @@ describe("NavbarView", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When
|
// When
|
||||||
render(<NavbarView user={mockUser} onSignOut={noop} canViewAuditLog />)
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={mockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
// There should be a 'B' avatar!
|
// There should be a 'B' avatar!
|
||||||
@ -64,16 +99,56 @@ describe("NavbarView", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("audit nav link has the correct href", async () => {
|
it("audit nav link has the correct href", async () => {
|
||||||
render(<NavbarView user={MockUser} onSignOut={noop} canViewAuditLog />)
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
const auditLink = await screen.findByText(navLanguage.audit)
|
const auditLink = await screen.findByText(navLanguage.audit)
|
||||||
expect((auditLink as HTMLAnchorElement).href).toContain("/audit")
|
expect((auditLink as HTMLAnchorElement).href).toContain("/audit")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("audit nav link is hidden for members", async () => {
|
it("audit nav link is hidden for members", async () => {
|
||||||
render(
|
render(
|
||||||
<NavbarView user={MockUser2} onSignOut={noop} canViewAuditLog={false} />,
|
<NavbarView
|
||||||
|
user={MockUser2}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog={false}
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
)
|
)
|
||||||
const auditLink = screen.queryByText(navLanguage.audit)
|
const auditLink = screen.queryByText(navLanguage.audit)
|
||||||
expect(auditLink).not.toBeInTheDocument()
|
expect(auditLink).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("deployment nav link has the correct href", async () => {
|
||||||
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog
|
||||||
|
canViewDeployment
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
const auditLink = await screen.findByText(navLanguage.deployment)
|
||||||
|
expect((auditLink as HTMLAnchorElement).href).toContain(
|
||||||
|
"/settings/deployment/general",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("deployment nav link is hidden for members", async () => {
|
||||||
|
render(
|
||||||
|
<NavbarView
|
||||||
|
user={MockUser2}
|
||||||
|
onSignOut={noop}
|
||||||
|
canViewAuditLog={false}
|
||||||
|
canViewDeployment={false}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
const auditLink = screen.queryByText(navLanguage.deployment)
|
||||||
|
expect(auditLink).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -17,6 +17,7 @@ export interface NavbarViewProps {
|
|||||||
user?: TypesGen.User
|
user?: TypesGen.User
|
||||||
onSignOut: () => void
|
onSignOut: () => void
|
||||||
canViewAuditLog: boolean
|
canViewAuditLog: boolean
|
||||||
|
canViewDeployment: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Language = {
|
export const Language = {
|
||||||
@ -24,11 +25,16 @@ export const Language = {
|
|||||||
templates: "Templates",
|
templates: "Templates",
|
||||||
users: "Users",
|
users: "Users",
|
||||||
audit: "Audit",
|
audit: "Audit",
|
||||||
|
deployment: "Deployment",
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavItems: React.FC<
|
const NavItems: React.FC<
|
||||||
React.PropsWithChildren<{ className?: string; canViewAuditLog: boolean }>
|
React.PropsWithChildren<{
|
||||||
> = ({ className, canViewAuditLog }) => {
|
className?: string
|
||||||
|
canViewAuditLog: boolean
|
||||||
|
canViewDeployment: boolean
|
||||||
|
}>
|
||||||
|
> = ({ className, canViewAuditLog, canViewDeployment }) => {
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
@ -65,6 +71,13 @@ const NavItems: React.FC<
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{canViewDeployment && (
|
||||||
|
<ListItem button className={styles.item}>
|
||||||
|
<NavLink className={styles.link} to="/settings/deployment/general">
|
||||||
|
{Language.deployment}
|
||||||
|
</NavLink>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</List>
|
</List>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -72,6 +85,7 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
|
|||||||
user,
|
user,
|
||||||
onSignOut,
|
onSignOut,
|
||||||
canViewAuditLog,
|
canViewAuditLog,
|
||||||
|
canViewDeployment,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
||||||
@ -98,7 +112,10 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
|
|||||||
<div className={styles.drawerHeader}>
|
<div className={styles.drawerHeader}>
|
||||||
<Logo fill="white" opacity={1} width={125} />
|
<Logo fill="white" opacity={1} width={125} />
|
||||||
</div>
|
</div>
|
||||||
<NavItems canViewAuditLog={canViewAuditLog} />
|
<NavItems
|
||||||
|
canViewAuditLog={canViewAuditLog}
|
||||||
|
canViewDeployment={canViewDeployment}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
@ -109,6 +126,7 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
|
|||||||
<NavItems
|
<NavItems
|
||||||
className={styles.desktopNavItems}
|
className={styles.desktopNavItems}
|
||||||
canViewAuditLog={canViewAuditLog}
|
canViewAuditLog={canViewAuditLog}
|
||||||
|
canViewDeployment={canViewDeployment}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.profileButton}>
|
<div className={styles.profileButton}>
|
||||||
@ -192,7 +210,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
padding: `${theme.spacing(1.5)}px ${theme.spacing(2)}px`,
|
padding: `${theme.spacing(1.5)}px ${theme.spacing(2)}px`,
|
||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
transition: "background-color 0.3s ease",
|
transition: "background-color 0.15s ease-in-out",
|
||||||
|
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.action.hover,
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
314
site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx
Normal file
314
site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
import Table from "@material-ui/core/Table"
|
||||||
|
import TableBody from "@material-ui/core/TableBody"
|
||||||
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
|
import TableContainer from "@material-ui/core/TableContainer"
|
||||||
|
import TableHead from "@material-ui/core/TableHead"
|
||||||
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
|
import {
|
||||||
|
Badges,
|
||||||
|
DisabledBadge,
|
||||||
|
EnabledBadge,
|
||||||
|
} from "components/DeploySettingsLayout/Badges"
|
||||||
|
import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout"
|
||||||
|
import { Header } from "components/DeploySettingsLayout/Header"
|
||||||
|
import {
|
||||||
|
OptionDescription,
|
||||||
|
OptionName,
|
||||||
|
OptionValue,
|
||||||
|
} from "components/DeploySettingsLayout/Option"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
const AuthSettingsPage: React.FC = () => {
|
||||||
|
const { deploymentFlags } = useDeploySettings()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack direction="column" spacing={6}>
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="Login with OpenID Connect"
|
||||||
|
secondary
|
||||||
|
description="Set up authentication to login with OpenID Connect."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/admin/auth#openid-connect-with-google"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Badges>
|
||||||
|
{deploymentFlags.oidc_client_id.value ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
</Badges>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="50%">Option</TableCell>
|
||||||
|
<TableCell width="50%">Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oidc_client_id.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oidc_client_id.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oidc_client_id.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oidc_client_secret.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oidc_client_secret.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oidc_client_secret.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oidc_allow_signups.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oidc_allow_signups.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oidc_allow_signups.value.toString()}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oidc_email_domain.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oidc_email_domain.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oidc_email_domain.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oidc_issuer_url.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oidc_issuer_url.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oidc_issuer_url.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>{deploymentFlags.oidc_scopes.name}</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oidc_scopes.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
<ul>
|
||||||
|
{deploymentFlags.oidc_scopes.value.map((scope) => (
|
||||||
|
<li key={scope}>{scope}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="Login with GitHub"
|
||||||
|
secondary
|
||||||
|
description="Set up authentication to login with GitHub."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/admin/auth#github"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Badges>
|
||||||
|
{deploymentFlags.oauth2_github_client_id.value ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
</Badges>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="50%">Option</TableCell>
|
||||||
|
<TableCell width="50%">Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oauth2_github_client_id.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oauth2_github_client_id.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oauth2_github_client_id.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oauth2_github_client_secret.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oauth2_github_client_secret.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oauth2_github_client_secret.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oauth2_github_allow_signups.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oauth2_github_allow_signups.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oauth2_github_allow_signups.value.toString()}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oauth2_github_allowed_organizations.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{
|
||||||
|
deploymentFlags.oauth2_github_allowed_organizations
|
||||||
|
.description
|
||||||
|
}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
<ul>
|
||||||
|
{deploymentFlags.oauth2_github_allowed_organizations.value.map(
|
||||||
|
(org) => (
|
||||||
|
<li key={org}>{org}</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oauth2_github_allowed_teams.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.oauth2_github_allowed_teams.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
<ul>
|
||||||
|
{deploymentFlags.oauth2_github_allowed_teams.value.map(
|
||||||
|
(team) => (
|
||||||
|
<li key={team}>{team}</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.oauth2_github_enterprise_base_url.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{
|
||||||
|
deploymentFlags.oauth2_github_enterprise_base_url
|
||||||
|
.description
|
||||||
|
}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.oauth2_github_enterprise_base_url.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthSettingsPage
|
85
site/src/pages/DeploySettingsPage/GeneralSettingsPage.tsx
Normal file
85
site/src/pages/DeploySettingsPage/GeneralSettingsPage.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import Table from "@material-ui/core/Table"
|
||||||
|
import TableBody from "@material-ui/core/TableBody"
|
||||||
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
|
import TableContainer from "@material-ui/core/TableContainer"
|
||||||
|
import TableHead from "@material-ui/core/TableHead"
|
||||||
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
|
import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout"
|
||||||
|
import { Header } from "components/DeploySettingsLayout/Header"
|
||||||
|
import {
|
||||||
|
OptionDescription,
|
||||||
|
OptionName,
|
||||||
|
OptionValue,
|
||||||
|
} from "components/DeploySettingsLayout/Option"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
const GeneralSettingsPage: React.FC = () => {
|
||||||
|
const { deploymentFlags } = useDeploySettings()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header
|
||||||
|
title="General"
|
||||||
|
description="Settings for accessing your Coder deployment."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/admin/auth#openid-connect-with-google"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="50%">Option</TableCell>
|
||||||
|
<TableCell width="50%">Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>{deploymentFlags.access_url.name}</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.access_url.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>{deploymentFlags.access_url.value}</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>{deploymentFlags.address.name}</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.address.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>{deploymentFlags.address.value}</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.wildcard_access_url.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.wildcard_access_url.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.wildcard_access_url.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GeneralSettingsPage
|
121
site/src/pages/DeploySettingsPage/NetworkSettingsPage.tsx
Normal file
121
site/src/pages/DeploySettingsPage/NetworkSettingsPage.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import Table from "@material-ui/core/Table"
|
||||||
|
import TableBody from "@material-ui/core/TableBody"
|
||||||
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
|
import TableContainer from "@material-ui/core/TableContainer"
|
||||||
|
import TableHead from "@material-ui/core/TableHead"
|
||||||
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
|
import {
|
||||||
|
DisabledBadge,
|
||||||
|
EnabledBadge,
|
||||||
|
} from "components/DeploySettingsLayout/Badges"
|
||||||
|
import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout"
|
||||||
|
import { Header } from "components/DeploySettingsLayout/Header"
|
||||||
|
import {
|
||||||
|
OptionDescription,
|
||||||
|
OptionName,
|
||||||
|
OptionValue,
|
||||||
|
} from "components/DeploySettingsLayout/Option"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
const NetworkSettingsPage: React.FC = () => {
|
||||||
|
const { deploymentFlags } = useDeploySettings()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="column" spacing={6}>
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="Network"
|
||||||
|
description="Configure your deployment connectivity."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/admin/auth#openid-connect-with-google"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="50%">Option</TableCell>
|
||||||
|
<TableCell width="50%">Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.derp_server_enabled.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.derp_server_enabled.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.derp_server_enabled.value ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.derp_server_region_name.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.derp_server_region_name.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.derp_server_region_name.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.derp_server_stun_address.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.derp_server_stun_address.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.derp_server_stun_address.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.derp_config_url.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.derp_config_url.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.derp_config_url.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NetworkSettingsPage
|
231
site/src/pages/DeploySettingsPage/SecuritySettingsPage.tsx
Normal file
231
site/src/pages/DeploySettingsPage/SecuritySettingsPage.tsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import Table from "@material-ui/core/Table"
|
||||||
|
import TableBody from "@material-ui/core/TableBody"
|
||||||
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
|
import TableContainer from "@material-ui/core/TableContainer"
|
||||||
|
import TableHead from "@material-ui/core/TableHead"
|
||||||
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
|
import { useActor } from "@xstate/react"
|
||||||
|
import { FeatureNames } from "api/types"
|
||||||
|
import {
|
||||||
|
Badges,
|
||||||
|
DisabledBadge,
|
||||||
|
EnabledBadge,
|
||||||
|
EnterpriseBadge,
|
||||||
|
} from "components/DeploySettingsLayout/Badges"
|
||||||
|
import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout"
|
||||||
|
import { Header } from "components/DeploySettingsLayout/Header"
|
||||||
|
import {
|
||||||
|
OptionDescription,
|
||||||
|
OptionName,
|
||||||
|
OptionValue,
|
||||||
|
} from "components/DeploySettingsLayout/Option"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import React, { useContext } from "react"
|
||||||
|
import { XServiceContext } from "xServices/StateContext"
|
||||||
|
|
||||||
|
const SecuritySettingsPage: React.FC = () => {
|
||||||
|
const { deploymentFlags } = useDeploySettings()
|
||||||
|
const xServices = useContext(XServiceContext)
|
||||||
|
const [entitlementsState] = useActor(xServices.entitlementsXService)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="column" spacing={6}>
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="Security"
|
||||||
|
description="Ensure your Coder deployment is secure."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/admin/auth#openid-connect-with-google"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="50%">Option</TableCell>
|
||||||
|
<TableCell width="50%">Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.ssh_keygen_algorithm.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.ssh_keygen_algorithm.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.ssh_keygen_algorithm.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.secure_auth_cookie.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.secure_auth_cookie.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.secure_auth_cookie.value ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="Audit Logging"
|
||||||
|
secondary
|
||||||
|
description="Allow auditors to monitor user operations in your deployment."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/admin/audit-logs"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Badges>
|
||||||
|
{entitlementsState.context.entitlements.features[
|
||||||
|
FeatureNames.AuditLog
|
||||||
|
].enabled ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
<EnterpriseBadge />
|
||||||
|
</Badges>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="Browser Only Connections"
|
||||||
|
secondary
|
||||||
|
description="Block all workspace access via SSH, port forward, and other non-browser connections."
|
||||||
|
docsHref="https://coder.com/docs/coder-oss/latest/networking#browser-only-connections-enterprise"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Badges>
|
||||||
|
{entitlementsState.context.entitlements.features[
|
||||||
|
FeatureNames.BrowserOnly
|
||||||
|
].enabled ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
<EnterpriseBadge />
|
||||||
|
</Badges>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
title="TLS"
|
||||||
|
secondary
|
||||||
|
description="Ensure TLS is properly configured for your Coder deployment."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="50%">Option</TableCell>
|
||||||
|
<TableCell width="50%">Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>{deploymentFlags.tls_enable.name}</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.tls_enable.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.tls_enable.value ? (
|
||||||
|
<EnabledBadge />
|
||||||
|
) : (
|
||||||
|
<DisabledBadge />
|
||||||
|
)}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>{deploymentFlags.tls_cert_files.name}</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.tls_cert_files.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
<ul>
|
||||||
|
{deploymentFlags.tls_cert_files.value.map(
|
||||||
|
(file, index) => (
|
||||||
|
<li key={index}>{file}</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>{deploymentFlags.tls_key_files.name}</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.tls_key_files.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
<ul>
|
||||||
|
{deploymentFlags.tls_key_files.value.map(
|
||||||
|
(file, index) => (
|
||||||
|
<li key={index}>{file}</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<OptionName>
|
||||||
|
{deploymentFlags.tls_min_version.name}
|
||||||
|
</OptionName>
|
||||||
|
<OptionDescription>
|
||||||
|
{deploymentFlags.tls_min_version.description}
|
||||||
|
</OptionDescription>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<OptionValue>
|
||||||
|
{deploymentFlags.tls_min_version.value}
|
||||||
|
</OptionValue>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SecuritySettingsPage
|
@ -3,6 +3,7 @@ import { createContext, FC, ReactNode } from "react"
|
|||||||
import { ActorRefFrom } from "xstate"
|
import { ActorRefFrom } from "xstate"
|
||||||
import { authMachine } from "./auth/authXService"
|
import { authMachine } from "./auth/authXService"
|
||||||
import { buildInfoMachine } from "./buildInfo/buildInfoXService"
|
import { buildInfoMachine } from "./buildInfo/buildInfoXService"
|
||||||
|
import { deploymentFlagsMachine } from "./deploymentFlags/deploymentFlagsMachine"
|
||||||
import { entitlementsMachine } from "./entitlements/entitlementsXService"
|
import { entitlementsMachine } from "./entitlements/entitlementsXService"
|
||||||
import { siteRolesMachine } from "./roles/siteRolesXService"
|
import { siteRolesMachine } from "./roles/siteRolesXService"
|
||||||
|
|
||||||
@ -11,6 +12,8 @@ interface XServiceContextType {
|
|||||||
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
|
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
|
||||||
entitlementsXService: ActorRefFrom<typeof entitlementsMachine>
|
entitlementsXService: ActorRefFrom<typeof entitlementsMachine>
|
||||||
siteRolesXService: ActorRefFrom<typeof siteRolesMachine>
|
siteRolesXService: ActorRefFrom<typeof siteRolesMachine>
|
||||||
|
// Since the info here is used by multiple deployment settings page and we don't want to refetch them every time
|
||||||
|
deploymentFlagsXService: ActorRefFrom<typeof deploymentFlagsMachine>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +34,7 @@ export const XServiceProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
|||||||
buildInfoXService: useInterpret(buildInfoMachine),
|
buildInfoXService: useInterpret(buildInfoMachine),
|
||||||
entitlementsXService: useInterpret(entitlementsMachine),
|
entitlementsXService: useInterpret(entitlementsMachine),
|
||||||
siteRolesXService: useInterpret(siteRolesMachine),
|
siteRolesXService: useInterpret(siteRolesMachine),
|
||||||
|
deploymentFlagsXService: useInterpret(deploymentFlagsMachine),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -16,6 +16,7 @@ export const checks = {
|
|||||||
createTemplates: "createTemplates",
|
createTemplates: "createTemplates",
|
||||||
deleteTemplates: "deleteTemplates",
|
deleteTemplates: "deleteTemplates",
|
||||||
viewAuditLog: "viewAuditLog",
|
viewAuditLog: "viewAuditLog",
|
||||||
|
viewDeploymentFlags: "viewDeploymentFlags",
|
||||||
createGroup: "createGroup",
|
createGroup: "createGroup",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
@ -56,6 +57,12 @@ export const permissionsToCheck = {
|
|||||||
},
|
},
|
||||||
action: "read",
|
action: "read",
|
||||||
},
|
},
|
||||||
|
[checks.viewDeploymentFlags]: {
|
||||||
|
object: {
|
||||||
|
resource_type: "deployment_flags",
|
||||||
|
},
|
||||||
|
action: "read",
|
||||||
|
},
|
||||||
[checks.createGroup]: {
|
[checks.createGroup]: {
|
||||||
object: {
|
object: {
|
||||||
resource_type: "group",
|
resource_type: "group",
|
||||||
@ -93,6 +100,7 @@ export type AuthEvent =
|
|||||||
| { type: "REGENERATE_SSH_KEY" }
|
| { type: "REGENERATE_SSH_KEY" }
|
||||||
| { type: "CONFIRM_REGENERATE_SSH_KEY" }
|
| { type: "CONFIRM_REGENERATE_SSH_KEY" }
|
||||||
| { type: "CANCEL_REGENERATE_SSH_KEY" }
|
| { type: "CANCEL_REGENERATE_SSH_KEY" }
|
||||||
|
| { type: "GET_AUTH_METHODS" }
|
||||||
|
|
||||||
export const authMachine =
|
export const authMachine =
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QEMCuAXAFgZXc9YAdLAJZQB2kA8hgMTYCSA4gHID6DLioADgPal0JPuW4gAHogBsATimEAzDIAcAFgUB2VTJ26ANCACeiAIwaADKsLKFtu-dsBfRwbRZc+IqQolyUBuS0ECJEvgBufADWXmTkAWL8gsKiSBKICubKhKpSyhoArAbGCGaqGoRSlVXVlfnOrhg4eATEsb7+gWAATl18XYQ8ADb4AGZ9ALatFPGpiSRCImKSCLLySmqa2ro6RaYFAEzWDscK9SBuTZ6EMOhCfgCqsN1BIYThUUQ3ALJgCQLzySW6Uy2VyBV2JXyUhMFRqcLqLnOjQ8LRudygj2e3V6-SGowm1zA6B+fySi1Sy32MnyhHyyksYK2ug0EJM1IURxO9jOFxRnyJ6IACt1xiRYKQRLAXpQ3uQItFCABjTBgRWRYVdUXi5LwWb-BYpUDLZRScygvKFIyIGQaQ4FHnI5r827tDVaiXkKXYvoDYboMaapUqtVusUe3W8fWAimIE1mnIW1l0rImOE1BENdxOwkuvw-LB8CBS4Iy94K75EzCFiMgOYGoEIZRUwgyVT5BQmfaW4omGxZGxcuwOrNXNHtfNVou0b24v0ByYVgtF0kA8lG2PN1vtzvd0zszmD06I3nZ7yUCABAa9EYkQahCB32j3QUAEQAggAVACibEFACUqAAMQYAAZL8V3rGMSjbGQKihLsISkOlaWHS4WjPSBLx4a9byIVAeAgfBXRwx8S1COUPkIE8rgwi9yCvPgbzvQh8MIoUSLABB3kVIiRAAbXMABdCDo3XaD8lgpCpAQq0EA0eTUL5KZzywjiWIIoi-EFDjpx6H08X9AlqPQ2JMPo7DGNw9S2OIyy7y4iieINAThL1MlDTScTJPg3dGxkfZFNPUy6OIWBMDeB8wFoJgvw-NhsGwAAJNgAGkvwATREtdPM7VQrE0XyTF7coB0PQKaOCy9xXCsc-ASxKUrAQxpXI+UiGMmIKDM0KaoFdp6sawwHIiJzkhcrKPOWIqkOscFZNTfYOXtY9HQqrqQuqnN0QGprdJxX18UDDrlO6zbaqgHahu43jyHGtzV0m0x9jyxQ5p7ExzBhUrB3Kkz1qqsLCEGPhkAgSAIsfP8vxilgvz-T8f3q1KMomht9g+8ocnMGQCtZDRZAPLkMyREc-pU+jNuB0HwcVEQb01S6-zAGBKC6TxaAAYTfFgOa-EC2ChmG4YR+KkuRzL7sgsT0bMQhzCUcxpMK20vsPBRieO2iAfCqmwYgJU6ZIBmksGpmWe6dmOaoFhgL-L4Behr9Yfh79ReStKJcjdy0Yx0Fsdx+b23MX7OvJnqgZBvXCC6ZmwFZzSLpN3ayNlNqqNWsnTsB3XwZj822e2pOrscm67q9h6GzMBQrDy7cZJ7DR1ZQlbSdDrOdcj3PY-jwuGt2mcDsMo6M7bjbs87-W87ji3e8G4a+FG-ihNRqCq5rtsO3r0wpG0ZvMzQ0eqtVVAunmQwIai5931d7Avw5+4-wYD9PdrKNsqmsoOQyBM3sQZ6pBDidDax9T7oHPqxBO2AQFnxaqnSimtKoU2gWA6ykDkHFxGqXZektRI5U-ooBkiZZIKFyHvEmB8gFH0VCfM+qDtroL2vpOcRkR6UKQdQ0B4CNL0I4Wfeei9brYPLlLPBjcCE-18m2KwGtWFa0CIwVgbAqD3A-CvaWWgYSEN-iUDIZpUxpiqBoQBZ52g0HQLAssoczFqM8k2WCW5N6+X2OYeShMTgyNbspUxdAB4GXnMpaxOD35-w0XLCRrJ0ZWH0QYqQRiW4UOVKqSI7RAJG1gOgTEXQLEUQVMdRJaoUlpIyU8Lo-CsGuWEbg5YmhCD7B3nU-YA5lA6HVhCZxYjoRshyDvJQ+NVCAIAO7IABH4QCfQPwqlSV0dJmT6DMHYJwGx1Sm7Yx0I3SwziTBOLqWaLQdIpC2HyKoLZ5gESInIIWOAYgEHrUCZU4JJRsY0iIT2akZoPEUJMX4GY9zHoIDbIcdGLzt4qFhDEpCgDzqZKWYgVQ+xWSyCyOC2okK+paRFGGHUML-mmnNNorZbIwUxI+Upc6E5qzYq2LUuQwKSitnymrI8+8lJyIYkxe8zELlfj0l0bFVcaRlCklvRspzjGILZVZEgkVCAzj5Y3AV+MfIQjyEy8hLLxUWXZRfOVRVsiKqVhCRuhxtgaBVY3A5MgxX-XMmpCB7E7K-CCX8oq+RnnaIKJa+J6rrUSrvHykwHZZq+X2bScwYbzUSTUBCr1QUfWbSlX6p1lctlusKp2Q4JLY1hzOmixOfdii-MrlIiotKPpyCtdm8e1N9YJsdYWqCmyshyH9vigoVhkWxIre3CO1aDbkHpuMRm3cZ51tft7BtqhzAZoVga+aGhexuOOJmtalaO69qnj3fqRc+UKHpIQIq87S25GXZnMea69Y7qhPuswxVCptnKNsOQ7Z2xUhPYfCmYV-WBtLVOjkJqzUkP6TGldp10EX0IFynlcroRywsI4iEu6ArAdPVQmhKDa0yqg0m1e+NNFwZ3BCNswdkPvuIGB2tcqakuPlgR4h2MWzMgAxartwDeEoLtf1dB-rXVBoQyQljqHOFfq+q2v9jHG7mqUKqm55N-UuN4-NT6DGdD5C0Gp-Ypq31eL8HcsdFcG0yA+rB5QtHijOKOYoNWgD8nJNGUU6F2GxK9LlvSTIcLGn5C2ZUNpLj5CxIUFSD6XmuyDOGeiMZXQJlgCmTMkp2LGm1OUFCHQORGkyEVqoZQbTNly2-poaERy6kh0pYl5LrZpLNIy1l2Sm5dAkPRs4wz+NlDOGcEAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QEMCuAXAFgZXc9YAdLAJZQB2kA8hgMTYCSA4gHID6DLioADgPal0JPuW4gAHogBsATimEAzDIAcAFgUB2VTJ26ANCACeiAIwaADKsLKFtu-dsBfRwbRZc+IqQolyUBuS0ECJEvgBufADWXmTkAWL8gsKiSBKICubKhKpSyhoArAbGCGaqGoRSlVXVlfnOrhg4eATEsb7+gWAATl18XYQ8ADb4AGZ9ALatFPGpiSRCImKSCLLySmqa2ro6RaYFAEzWDscK9SBuTZ6EMOhCfgCqsN1BIYThUUQ3ALJgCQLzySW6Uy2VyBV2JXyUhMFRqcLqLnOjQ8LRudygj2e3V6-SGowm1zA6B+fySi1Sy32MnyhHyyksYK2ug0EJM1IURxO9jOFxRnyJ6IACt1xiRYKQRLAXpQ3uQItFCABjTBgRWRYVdUXi5LwWb-BYpUDLZRScygvKFIyIGQaQ4FHnI5r827tDVaiXkKXYvoDYboMaapUqtVusUe3W8fWAimIE1mnIW1l0rImOE1BENdxOwkuvw-LB8CBS4Iy94K75EzCFiMgOYGoEIZRUwgyVT5BQmfaW4omGxZGxcuwOrNXNHtfNVou0b24v0ByYVgtF0kA8lG2PN1vtzvd0zszmD06I3nZ7yUCABAa9EYkQahCB32j3QUAEQAggAVACibEFACUqAAMQYAAZL8V3rGMSjbGQKihLsISkOlaWHS4WjPSBLx4a9byIVAeAgfBXRwx8S1COUPkIE8rgwi9yCvPgbzvQh8MIoUSLABB3kVIiRAAbXMABdCDo3XaD8lgpCpAQq0EA0eTUL5KZzywjiWIIoi-EFDjpx6H08X9AlqPQ2JMPo7DGNw9S2OIyy7y4iieINAThL1MlDTScTJPg3dGxkfZFNPUy6OIWBMDeB8wFoJgvw-NhsGwAAJNgAGkvwATREtdPM7VQrE0XyTF7coB0PQKaOCy9xXCsc-ASxKUrAQxpXI+UiGMmIKDM0KaoFdp6sawwHIiJzkhcrKPOWIqkOscFZNTfYOXtY9HQqrqQuqnN0QGprdJxX18UDDrlO6zbaqgHahu43jyHGtzV0m0x9jyxQ5p7ExzBhUrB3Kkz1qqsLCEGPhkAgSAIsfP8vxilgvz-T8f3q1KMomht9g+8ocnMGQCtZDRZAPLkMyREc-pU+jNuB0HwcVEQb01S6-zAGBKC6TxaAAYTfFgOa-EC2ChmG4YR+KkuRzL7sgsT0bMQhzCUcxpMK20vsPBRieO2iAfCqmwYgJU6ZIBmksGpmWe6dmOaoFhgL-L4Behr9Yfh79ReStKJcjdy0Yx0Fsdx+b23MX7OvJnqgZBvXCC6ZmwFZzSLpN3ayNlNqqNWsnTsB3XwZj822e2pOrscm67q9h6GzMBQrDy7cZJ7DR1ZQlbSdDrOdcj3PY-jwuGt2mcDsMo6M7bjbs87-W87ji3e8G4a+FG-ihNRqCq5rtsO3r0wpG0ZvMzQ0eqtVVAunmQwIai5931d7Avw5+4-wYD9PdrKNsqmsoOQyBM3sQZ6pBDidDax9T7oHPqxBO2AQFnxaqnSimtKoU2gWA6ykDkHFxGqXZektRI5U-ooBkiZZIKFyHvEmB8gFH0VCfM+qDtroL2vpOcRkR6UKQdQ0B4CNL0I4Wfeei9brYPLlLPBjcCE-18m2KwGtWFa0CIwVgbAqD3A-CvaWWgYSEN-iUDIZpUxpiqBoQBZ52g0HQLAssoczFqM8k2WCW5N6+X2OYeShMTgyNbspUxdAB4GXnMpaxOD35-w0XLCRrJ0ZWH0QYqQRiW4UOVKqSI7RAJG1gOgTEXQLEUQVMdRJaoUlpIyU8Lo-CsGuWEbg5YmhCD7B3nU-YA5lA6HVhCZxYjoRshyDvJQ+NVCAIAO7IABH4QCfQPwqlSV0dJmT6DMHYJwGx1Sm7Yx0I3SwziTBOLqWaLQdIpC2HyKoLZ5gESInIIWOAYgEHrUCZU4JJRsY0iIT2akZoPEUJMX4GY9zHoIDbIcdGLzt4qFhDEpCgDzqZKWYgVQ+xWSyCyOC2okK+paRFGGHUML-mmnNNorZbIwUxI+Upc6E5qzYq2LUuQwKSitnymrI8+8lJyIYkxe8zELlfj0l0bFVcaRlCklvRspzjGILZVZEgkVCAzj5Y3AV+MfIQjyEy8hLLxUWXZRfOVRVsiKqVhCRuhxtgaBVY3A5MgxX-XMmpCB7E7K-CCX8oq+RnnaIKJa+J6rrUSrvHykwHZZq+X2bScwYbzUSTUBCr1QUfWbSlX6p1lctlusKp2Q4JLY1hzOmixOfdii-MrlIiotKPpyCtdm8e1N9YJsdYWqCmyshyH9vigoVhkWxIre3CO1aDbkHpuMRm3cZ51tft7BtqhzAZoVga+aGhexuOOJmtalaO69qnj3fqRc+UKHpIQIq87S25GXZnMea69Y7qhPuswxVCptnKNsOQ7Z2xUhPYfCmYV-WBtLVOjkJqzUkP6TGldp10EX0IFynlcroRywsI4iEu6ArAdPVQmhKDa0yqg0m1e+NNFwZ3BCNswdkPvuIGB2tcqakuPlgR4h2MWzMgAxartwDeEoLtf1dB-rXVBoQyQljqHOFfq+q2v9jHG7mqUKqm55N-UuN4-NT6DGdD5C0Gp-Ypq31eL8HcsdFcG0yA+rB5QtHijOKOYoNWgD8nJNGUU6F2GxK9LlvSTIcLGn5C2ZUNpLj5CxIUFSD6XmuyDOGeiMZXQJlgCmTMkp2LGm1OUFCHQORGkyEVqoZQbTNly2-poaERy6kh0pYl5LrZpLNIy1l2Sm5dAkPRs4wz+NlDOGcEAA */
|
||||||
@ -340,6 +348,36 @@ export const authMachine =
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
initial: "idle",
|
||||||
|
states: {
|
||||||
|
idle: {
|
||||||
|
on: {
|
||||||
|
GET_AUTH_METHODS: {
|
||||||
|
target: "gettingMethods",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gettingMethods: {
|
||||||
|
entry: "clearGetMethodsError",
|
||||||
|
invoke: {
|
||||||
|
src: "getMethods",
|
||||||
|
onDone: [
|
||||||
|
{
|
||||||
|
actions: ["assignMethods", "clearGetMethodsError"],
|
||||||
|
target: "idle",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onError: [
|
||||||
|
{
|
||||||
|
actions: "assignGetMethodsError",
|
||||||
|
target: "idle",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
security: {
|
security: {
|
||||||
initial: "idle",
|
initial: "idle",
|
||||||
states: {
|
states: {
|
||||||
|
61
site/src/xServices/deploymentFlags/deploymentFlagsMachine.ts
Normal file
61
site/src/xServices/deploymentFlags/deploymentFlagsMachine.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { getDeploymentFlags } from "api/api"
|
||||||
|
import { DeploymentFlags } from "api/typesGenerated"
|
||||||
|
import { createMachine, assign } from "xstate"
|
||||||
|
|
||||||
|
export const deploymentFlagsMachine = createMachine(
|
||||||
|
{
|
||||||
|
id: "deploymentFlagsMachine",
|
||||||
|
initial: "idle",
|
||||||
|
schema: {
|
||||||
|
context: {} as {
|
||||||
|
deploymentFlags?: DeploymentFlags
|
||||||
|
getDeploymentFlagsError?: unknown
|
||||||
|
},
|
||||||
|
events: {} as { type: "LOAD" },
|
||||||
|
services: {} as {
|
||||||
|
getDeploymentFlags: {
|
||||||
|
data: DeploymentFlags
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tsTypes: {} as import("./deploymentFlagsMachine.typegen").Typegen0,
|
||||||
|
states: {
|
||||||
|
idle: {
|
||||||
|
on: {
|
||||||
|
LOAD: {
|
||||||
|
target: "loading",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
invoke: {
|
||||||
|
src: "getDeploymentFlags",
|
||||||
|
onDone: {
|
||||||
|
target: "loaded",
|
||||||
|
actions: ["assignDeploymentFlags"],
|
||||||
|
},
|
||||||
|
onError: {
|
||||||
|
target: "idle",
|
||||||
|
actions: ["assignGetDeploymentFlagsError"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loaded: {
|
||||||
|
type: "final",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
services: {
|
||||||
|
getDeploymentFlags,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
assignDeploymentFlags: assign({
|
||||||
|
deploymentFlags: (_, { data }) => data,
|
||||||
|
}),
|
||||||
|
assignGetDeploymentFlagsError: assign({
|
||||||
|
getDeploymentFlagsError: (_, { data }) => data,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
Reference in New Issue
Block a user