Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6b1b3a2

Browse files
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 <[email protected]>
1 parent 9b5d627 commit 6b1b3a2

19 files changed

+1420
-15
lines changed

codersdk/flags.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type DeploymentFlags struct {
3838
OAuth2GithubEnterpriseBaseURL *StringFlag `json:"oauth2_github_enterprise_base_url" typescript:",notnull"`
3939
OIDCAllowSignups *BoolFlag `json:"oidc_allow_signups" typescript:",notnull"`
4040
OIDCClientID *StringFlag `json:"oidc_client_id" typescript:",notnull"`
41-
OIDCClientSecret *StringFlag `json:"oidc_cliet_secret" typescript:",notnull"`
41+
OIDCClientSecret *StringFlag `json:"oidc_client_secret" typescript:",notnull"`
4242
OIDCEmailDomain *StringFlag `json:"oidc_email_domain" typescript:",notnull"`
4343
OIDCIssuerURL *StringFlag `json:"oidc_issuer_url" typescript:",notnull"`
4444
OIDCScopes *StringArrayFlag `json:"oidc_scopes" typescript:",notnull"`
@@ -49,7 +49,7 @@ type DeploymentFlags struct {
4949
TLSCertFiles *StringArrayFlag `json:"tls_cert_files" typescript:",notnull"`
5050
TLSClientCAFile *StringFlag `json:"tls_client_ca_file" typescript:",notnull"`
5151
TLSClientAuth *StringFlag `json:"tls_client_auth" typescript:",notnull"`
52-
TLSKeyFiles *StringArrayFlag `json:"tls_key_tiles" typescript:",notnull"`
52+
TLSKeyFiles *StringArrayFlag `json:"tls_key_files" typescript:",notnull"`
5353
TLSMinVersion *StringFlag `json:"tls_min_version" typescript:",notnull"`
5454
TraceEnable *BoolFlag `json:"trace_enable" typescript:",notnull"`
5555
SecureAuthCookie *BoolFlag `json:"secure_auth_cookie" typescript:",notnull"`

site/src/AppRouter.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { XServiceContext } from "xServices/StateContext"
2121
import { AuthAndFrame } from "./components/AuthAndFrame/AuthAndFrame"
2222
import { RequireAuth } from "./components/RequireAuth/RequireAuth"
2323
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
24+
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"
2425

2526
// Lazy load pages
2627
// - Pages that are secondary, not in the main navigation or not usually accessed
@@ -67,6 +68,18 @@ const GroupPage = lazy(() => import("./pages/GroupsPage/GroupPage"))
6768
const SettingsGroupPage = lazy(
6869
() => import("./pages/GroupsPage/SettingsGroupPage"),
6970
)
71+
const GeneralSettingsPage = lazy(
72+
() => import("./pages/DeploySettingsPage/GeneralSettingsPage"),
73+
)
74+
const SecuritySettingsPage = lazy(
75+
() => import("./pages/DeploySettingsPage/SecuritySettingsPage"),
76+
)
77+
const AuthSettingsPage = lazy(
78+
() => import("./pages/DeploySettingsPage/AuthSettingsPage"),
79+
)
80+
const NetworkSettingsPage = lazy(
81+
() => import("./pages/DeploySettingsPage/NetworkSettingsPage"),
82+
)
7083

7184
export const AppRouter: FC = () => {
7285
const xServices = useContext(XServiceContext)
@@ -237,6 +250,65 @@ export const AppRouter: FC = () => {
237250
/>
238251
</Route>
239252

253+
<Route path="/settings/deployment">
254+
<Route
255+
path="general"
256+
element={
257+
<AuthAndFrame>
258+
<RequirePermission
259+
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
260+
>
261+
<DeploySettingsLayout>
262+
<GeneralSettingsPage />
263+
</DeploySettingsLayout>
264+
</RequirePermission>
265+
</AuthAndFrame>
266+
}
267+
/>
268+
<Route
269+
path="security"
270+
element={
271+
<AuthAndFrame>
272+
<RequirePermission
273+
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
274+
>
275+
<DeploySettingsLayout>
276+
<SecuritySettingsPage />
277+
</DeploySettingsLayout>
278+
</RequirePermission>
279+
</AuthAndFrame>
280+
}
281+
/>
282+
<Route
283+
path="network"
284+
element={
285+
<AuthAndFrame>
286+
<RequirePermission
287+
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
288+
>
289+
<DeploySettingsLayout>
290+
<NetworkSettingsPage />
291+
</DeploySettingsLayout>
292+
</RequirePermission>
293+
</AuthAndFrame>
294+
}
295+
/>
296+
<Route
297+
path="auth"
298+
element={
299+
<AuthAndFrame>
300+
<RequirePermission
301+
isFeatureVisible={Boolean(permissions?.viewDeploymentFlags)}
302+
>
303+
<DeploySettingsLayout>
304+
<AuthSettingsPage />
305+
</DeploySettingsLayout>
306+
</RequirePermission>
307+
</AuthAndFrame>
308+
}
309+
/>
310+
</Route>
311+
240312
<Route path="settings" element={<SettingsLayout />}>
241313
<Route path="account" element={<AccountPage />} />
242314
<Route path="security" element={<SecurityPage />} />

site/src/api/api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,3 +641,14 @@ export const getAgentListeningPorts = async (
641641
)
642642
return response.data
643643
}
644+
645+
export const getDeploymentFlags =
646+
async (): Promise<TypesGen.DeploymentFlags> => {
647+
const response = await axios.get(`/api/v2/flags/deployment`)
648+
return response.data
649+
}
650+
651+
export const getReplicas = async (): Promise<TypesGen.Replica[]> => {
652+
const response = await axios.get(`/api/v2/replicas`)
653+
return response.data
654+
}

site/src/api/typesGenerated.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ export interface DeploymentFlags {
293293
readonly oauth2_github_enterprise_base_url: StringFlag
294294
readonly oidc_allow_signups: BoolFlag
295295
readonly oidc_client_id: StringFlag
296-
readonly oidc_cliet_secret: StringFlag
296+
readonly oidc_client_secret: StringFlag
297297
readonly oidc_email_domain: StringFlag
298298
readonly oidc_issuer_url: StringFlag
299299
readonly oidc_scopes: StringArrayFlag
@@ -304,7 +304,7 @@ export interface DeploymentFlags {
304304
readonly tls_cert_files: StringArrayFlag
305305
readonly tls_client_ca_file: StringFlag
306306
readonly tls_client_auth: StringFlag
307-
readonly tls_key_tiles: StringArrayFlag
307+
readonly tls_key_files: StringArrayFlag
308308
readonly tls_min_version: StringFlag
309309
readonly trace_enable: BoolFlag
310310
readonly secure_auth_cookie: BoolFlag
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import { Stack } from "components/Stack/Stack"
3+
import React, { PropsWithChildren } from "react"
4+
import { combineClasses } from "util/combineClasses"
5+
6+
export const EnabledBadge: React.FC = () => {
7+
const styles = useStyles()
8+
return (
9+
<span className={combineClasses([styles.badge, styles.enabledBadge])}>
10+
Enabled
11+
</span>
12+
)
13+
}
14+
15+
export const DisabledBadge: React.FC = () => {
16+
const styles = useStyles()
17+
return (
18+
<span className={combineClasses([styles.badge, styles.disabledBadge])}>
19+
Disabled
20+
</span>
21+
)
22+
}
23+
24+
export const EnterpriseBadge: React.FC = () => {
25+
const styles = useStyles()
26+
return (
27+
<span className={combineClasses([styles.badge, styles.enterpriseBadge])}>
28+
Enterprise
29+
</span>
30+
)
31+
}
32+
33+
export const Badges: React.FC<PropsWithChildren> = ({ children }) => {
34+
const styles = useStyles()
35+
return (
36+
<Stack
37+
className={styles.badges}
38+
direction="row"
39+
alignItems="center"
40+
spacing={1}
41+
>
42+
{children}
43+
</Stack>
44+
)
45+
}
46+
47+
const useStyles = makeStyles((theme) => ({
48+
badges: {
49+
margin: theme.spacing(0, 0, 2),
50+
},
51+
52+
badge: {
53+
fontSize: 10,
54+
height: 24,
55+
fontWeight: 600,
56+
textTransform: "uppercase",
57+
letterSpacing: "0.085em",
58+
padding: theme.spacing(0, 1.5),
59+
borderRadius: 9999,
60+
display: "flex",
61+
alignItems: "center",
62+
width: "fit-content",
63+
},
64+
65+
enterpriseBadge: {
66+
backgroundColor: theme.palette.info.dark,
67+
border: `1px solid ${theme.palette.info.light}`,
68+
},
69+
70+
enabledBadge: {
71+
border: `1px solid ${theme.palette.success.light}`,
72+
backgroundColor: theme.palette.success.dark,
73+
},
74+
75+
disabledBadge: {
76+
border: `1px solid ${theme.palette.divider}`,
77+
backgroundColor: theme.palette.background.paper,
78+
},
79+
}))
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import { Margins } from "components/Margins/Margins"
3+
import { Stack } from "components/Stack/Stack"
4+
import { Sidebar } from "./Sidebar"
5+
import React, {
6+
createContext,
7+
PropsWithChildren,
8+
useContext,
9+
useEffect,
10+
} from "react"
11+
import { useActor } from "@xstate/react"
12+
import { XServiceContext } from "xServices/StateContext"
13+
import { Loader } from "components/Loader/Loader"
14+
import { DeploymentFlags } from "api/typesGenerated"
15+
16+
type DeploySettingsContextValue = { deploymentFlags: DeploymentFlags }
17+
18+
const DeploySettingsContext = createContext<
19+
DeploySettingsContextValue | undefined
20+
>(undefined)
21+
22+
export const useDeploySettings = (): DeploySettingsContextValue => {
23+
const context = useContext(DeploySettingsContext)
24+
if (!context) {
25+
throw new Error(
26+
"useDeploySettings should be used inside of DeploySettingsLayout",
27+
)
28+
}
29+
return context
30+
}
31+
32+
export const DeploySettingsLayout: React.FC<PropsWithChildren> = ({
33+
children,
34+
}) => {
35+
const xServices = useContext(XServiceContext)
36+
const [state, send] = useActor(xServices.deploymentFlagsXService)
37+
const styles = useStyles()
38+
const { deploymentFlags } = state.context
39+
40+
useEffect(() => {
41+
if (state.matches("idle")) {
42+
send("LOAD")
43+
}
44+
}, [send, state])
45+
46+
return (
47+
<Margins>
48+
<Stack className={styles.wrapper} direction="row" spacing={5}>
49+
<Sidebar />
50+
<main className={styles.content}>
51+
{deploymentFlags ? (
52+
<DeploySettingsContext.Provider value={{ deploymentFlags }}>
53+
{children}
54+
</DeploySettingsContext.Provider>
55+
) : (
56+
<Loader />
57+
)}
58+
</main>
59+
</Stack>
60+
</Margins>
61+
)
62+
}
63+
64+
const useStyles = makeStyles((theme) => ({
65+
wrapper: {
66+
padding: theme.spacing(6, 0),
67+
},
68+
69+
content: {
70+
maxWidth: 800,
71+
width: "100%",
72+
},
73+
}))
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Button from "@material-ui/core/Button"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import LaunchOutlined from "@material-ui/icons/LaunchOutlined"
4+
import { Stack } from "components/Stack/Stack"
5+
import React from "react"
6+
7+
export const Header: React.FC<{
8+
title: string | JSX.Element
9+
description: string | JSX.Element
10+
secondary?: boolean
11+
docsHref?: string
12+
}> = ({ title, description, docsHref, secondary }) => {
13+
const styles = useStyles()
14+
15+
return (
16+
<Stack alignItems="baseline" direction="row" justifyContent="space-between">
17+
<div className={styles.headingGroup}>
18+
<h1 className={`${styles.title} ${secondary ? "secondary" : ""}`}>
19+
{title}
20+
</h1>
21+
<span className={styles.description}>{description}</span>
22+
</div>
23+
24+
{docsHref && (
25+
<Button
26+
size="small"
27+
startIcon={<LaunchOutlined />}
28+
component="a"
29+
href={docsHref}
30+
target="_blank"
31+
variant="outlined"
32+
>
33+
Read the docs
34+
</Button>
35+
)}
36+
</Stack>
37+
)
38+
}
39+
40+
const useStyles = makeStyles((theme) => ({
41+
headingGroup: {
42+
maxWidth: 420,
43+
marginBottom: theme.spacing(3),
44+
},
45+
46+
title: {
47+
fontSize: 32,
48+
fontWeight: 700,
49+
display: "flex",
50+
alignItems: "center",
51+
lineHeight: "initial",
52+
margin: 0,
53+
marginBottom: theme.spacing(0.5),
54+
gap: theme.spacing(1),
55+
56+
"&.secondary": {
57+
fontSize: 24,
58+
fontWeight: 500,
59+
},
60+
},
61+
62+
description: {
63+
fontSize: 14,
64+
color: theme.palette.text.secondary,
65+
lineHeight: "160%",
66+
},
67+
}))

0 commit comments

Comments
 (0)