Skip to content

Commit 318234d

Browse files
committed
Add deployment config page
1 parent b12765c commit 318234d

File tree

13 files changed

+189
-20
lines changed

13 files changed

+189
-20
lines changed

cli/deployment/config.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,11 @@ func readSliceFromViper[T any](vip *viper.Viper, key string, value any) []T {
470470
if instance == nil {
471471
break
472472
}
473-
returnValues = append(returnValues, instance.Interface().(T))
473+
value, ok := instance.Interface().(T)
474+
if !ok {
475+
continue
476+
}
477+
returnValues = append(returnValues, value)
474478
}
475479
return returnValues
476480
}

coderd/coderdtest/authorize.go

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
5757
"POST:/api/v2/workspaceagents/aws-instance-identity": {NoAuthorize: true},
5858
"POST:/api/v2/workspaceagents/azure-instance-identity": {NoAuthorize: true},
5959
"POST:/api/v2/workspaceagents/google-instance-identity": {NoAuthorize: true},
60+
"GET:/api/v2/workspaceagents/me/gitauth": {NoAuthorize: true},
6061
"GET:/api/v2/workspaceagents/me/gitsshkey": {NoAuthorize: true},
6162
"GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true},
6263
"GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true},

codersdk/workspaceagents.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -655,13 +655,14 @@ type WorkspaceAgentGitAuthResponse struct {
655655
}
656656

657657
// WorkspaceAgentGitAuth submits a URL to fetch a GIT_ASKPASS username
658-
// and password for. If the URL doesn't match
658+
// and password for.
659+
// nolint:revive
659660
func (c *Client) WorkspaceAgentGitAuth(ctx context.Context, gitURL string, listen bool) (WorkspaceAgentGitAuthResponse, error) {
660-
url := "/api/v2/workspaceagents/me/gitauth?url=" + url.QueryEscape(gitURL)
661+
reqURL := "/api/v2/workspaceagents/me/gitauth?url=" + url.QueryEscape(gitURL)
661662
if listen {
662-
url += "&listen"
663+
reqURL += "&listen"
663664
}
664-
res, err := c.Request(ctx, http.MethodGet, url, nil)
665+
res, err := c.Request(ctx, http.MethodGet, reqURL, nil)
665666
if err != nil {
666667
return WorkspaceAgentGitAuthResponse{}, xerrors.Errorf("execute request: %w", err)
667668
}

enterprise/coderd/license/license_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func TestEntitlements(t *testing.T) {
2626
codersdk.FeatureWorkspaceQuota: true,
2727
codersdk.FeatureHighAvailability: true,
2828
codersdk.FeatureTemplateRBAC: true,
29+
codersdk.FeatureMultipleGitAuth: true,
2930
}
3031

3132
t.Run("Defaults", func(t *testing.T) {
@@ -68,6 +69,7 @@ func TestEntitlements(t *testing.T) {
6869
WorkspaceQuota: true,
6970
HighAvailability: true,
7071
TemplateRBAC: true,
72+
MultipleGitAuth: true,
7173
}),
7274
Exp: time.Now().Add(time.Hour),
7375
})

enterprise/coderd/licenses_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ func TestGetLicense(t *testing.T) {
108108
codersdk.FeatureWorkspaceQuota: json.Number("0"),
109109
codersdk.FeatureHighAvailability: json.Number("0"),
110110
codersdk.FeatureTemplateRBAC: json.Number("1"),
111+
codersdk.FeatureMultipleGitAuth: json.Number("0"),
111112
}, licenses[0].Claims["features"])
112113
assert.Equal(t, int32(2), licenses[1].ID)
113114
assert.Equal(t, "testing2", licenses[1].Claims["account_id"])
@@ -120,6 +121,7 @@ func TestGetLicense(t *testing.T) {
120121
codersdk.FeatureWorkspaceQuota: json.Number("0"),
121122
codersdk.FeatureHighAvailability: json.Number("0"),
122123
codersdk.FeatureTemplateRBAC: json.Number("0"),
124+
codersdk.FeatureMultipleGitAuth: json.Number("0"),
123125
}, licenses[1].Claims["features"])
124126
})
125127
}

site/src/AppRouter.tsx

+22-7
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,16 @@ const GeneralSettingsPage = lazy(
7474
const SecuritySettingsPage = lazy(
7575
() => import("./pages/DeploySettingsPage/SecuritySettingsPage"),
7676
)
77-
const AuthSettingsPage = lazy(
78-
() => import("./pages/DeploySettingsPage/AuthSettingsPage"),
77+
const UserAuthSettingsPage = lazy(
78+
() => import("./pages/DeploySettingsPage/UserAuthSettingsPage"),
79+
)
80+
const GitAuthSettingsPage = lazy(
81+
() => import("./pages/DeploySettingsPage/GitAuthSettingsPage"),
7982
)
8083
const NetworkSettingsPage = lazy(
8184
() => import("./pages/DeploySettingsPage/NetworkSettingsPage"),
8285
)
83-
const GitAuthPage = lazy(
84-
() => import("./pages/GitAuthPage/GitAuthPage")
85-
)
86+
const GitAuthPage = lazy(() => import("./pages/GitAuthPage/GitAuthPage"))
8687

8788
export const AppRouter: FC = () => {
8889
const xServices = useContext(XServiceContext)
@@ -305,14 +306,28 @@ export const AppRouter: FC = () => {
305306
}
306307
/>
307308
<Route
308-
path="auth"
309+
path="userauth"
310+
element={
311+
<AuthAndFrame>
312+
<RequirePermission
313+
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
314+
>
315+
<DeploySettingsLayout>
316+
<UserAuthSettingsPage />
317+
</DeploySettingsLayout>
318+
</RequirePermission>
319+
</AuthAndFrame>
320+
}
321+
/>
322+
<Route
323+
path="gitauth"
309324
element={
310325
<AuthAndFrame>
311326
<RequirePermission
312327
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
313328
>
314329
<DeploySettingsLayout>
315-
<AuthSettingsPage />
330+
<GitAuthSettingsPage />
316331
</DeploySettingsLayout>
317332
</RequirePermission>
318333
</AuthAndFrame>

site/src/components/AlertBanner/alertTypes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ApiError } from "api/errors"
22
import { ReactElement } from "react"
33

4-
export type Severity = "warning" | "error"
4+
export type Severity = "warning" | "error" | "info"
55

66
export interface AlertBannerProps {
77
severity: Severity

site/src/components/AlertBanner/severityConstants.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ReportProblemOutlinedIcon from "@material-ui/icons/ReportProblemOutlined"
22
import ErrorOutlineOutlinedIcon from "@material-ui/icons/ErrorOutlineOutlined"
3+
import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined"
34
import { colors } from "theme/colors"
45
import { Severity } from "./alertTypes"
56
import { ReactElement } from "react"
@@ -26,4 +27,10 @@ export const severityConstants: Record<
2627
/>
2728
),
2829
},
30+
info: {
31+
color: colors.blue[7],
32+
icon: (
33+
<InfoOutlinedIcon fontSize="small" style={{ color: colors.blue[7] }} />
34+
),
35+
},
2936
}

site/src/components/DeploySettingsLayout/Sidebar.tsx

+24-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@ import LaunchOutlined from "@material-ui/icons/LaunchOutlined"
33
import LockRounded from "@material-ui/icons/LockRounded"
44
import Globe from "@material-ui/icons/Public"
55
import VpnKeyOutlined from "@material-ui/icons/VpnKeyOutlined"
6+
import { useSelector } from "@xstate/react"
7+
import { GitIcon } from "components/Icons/GitIcon"
68
import { Stack } from "components/Stack/Stack"
7-
import React, { ElementType, PropsWithChildren, ReactNode } from "react"
9+
import React, {
10+
ElementType,
11+
PropsWithChildren,
12+
ReactNode,
13+
useContext,
14+
} from "react"
815
import { NavLink } from "react-router-dom"
916
import { combineClasses } from "util/combineClasses"
17+
import { XServiceContext } from "../../xServices/StateContext"
1018

1119
const SidebarNavItem: React.FC<
1220
PropsWithChildren<{ href: string; icon: ReactNode }>
@@ -39,6 +47,11 @@ const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
3947

4048
export const Sidebar: React.FC = () => {
4149
const styles = useStyles()
50+
const xServices = useContext(XServiceContext)
51+
const experimental = useSelector(
52+
xServices.entitlementsXService,
53+
(state) => state.context.entitlements.experimental,
54+
)
4255

4356
return (
4457
<nav className={styles.sidebar}>
@@ -49,11 +62,19 @@ export const Sidebar: React.FC = () => {
4962
General
5063
</SidebarNavItem>
5164
<SidebarNavItem
52-
href="../auth"
65+
href="../userauth"
5366
icon={<SidebarNavItemIcon icon={VpnKeyOutlined} />}
5467
>
55-
Authentication
68+
User Authentication
5669
</SidebarNavItem>
70+
{experimental && (
71+
<SidebarNavItem
72+
href="../gitauth"
73+
icon={<SidebarNavItemIcon icon={GitIcon} />}
74+
>
75+
Git Authentication
76+
</SidebarNavItem>
77+
)}
5778
<SidebarNavItem
5879
href="../network"
5980
icon={<SidebarNavItemIcon icon={Globe} />}

site/src/components/Icons/GitIcon.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon"
2+
3+
export const GitIcon: typeof SvgIcon = (props: SvgIconProps) => (
4+
<SvgIcon {...props} viewBox="0 0 96 96">
5+
<path d="M92.71 44.408 52.591 4.291c-2.31-2.311-6.057-2.311-8.369 0l-8.33 8.332L46.459 23.19c2.456-.83 5.272-.273 7.229 1.685 1.969 1.97 2.521 4.81 1.67 7.275l10.186 10.185c2.465-.85 5.307-.3 7.275 1.671 2.75 2.75 2.75 7.206 0 9.958-2.752 2.751-7.208 2.751-9.961 0-2.068-2.07-2.58-5.11-1.531-7.658l-9.5-9.499v24.997c.67.332 1.303.774 1.861 1.332 2.75 2.75 2.75 7.206 0 9.959-2.75 2.749-7.209 2.749-9.957 0-2.75-2.754-2.75-7.21 0-9.959.68-.679 1.467-1.193 2.307-1.537v-25.23c-.84-.344-1.625-.853-2.307-1.537-2.083-2.082-2.584-5.14-1.516-7.698L31.798 16.715 4.288 44.222c-2.311 2.313-2.311 6.06 0 8.371l40.121 40.118c2.31 2.311 6.056 2.311 8.369 0L92.71 52.779c2.311-2.311 2.311-6.06 0-8.371z" />
6+
</SvgIcon>
7+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import Table from "@material-ui/core/Table"
3+
import TableBody from "@material-ui/core/TableBody"
4+
import TableCell from "@material-ui/core/TableCell"
5+
import TableContainer from "@material-ui/core/TableContainer"
6+
import TableHead from "@material-ui/core/TableHead"
7+
import TableRow from "@material-ui/core/TableRow"
8+
import { AlertBanner } from "components/AlertBanner/AlertBanner"
9+
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges"
10+
import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout"
11+
import { Header } from "components/DeploySettingsLayout/Header"
12+
import React from "react"
13+
import { Helmet } from "react-helmet-async"
14+
import { pageTitle } from "util/page"
15+
16+
const GitAuthSettingsPage: React.FC = () => {
17+
const styles = useStyles()
18+
const { deploymentConfig: deploymentConfig } = useDeploySettings()
19+
20+
return (
21+
<>
22+
<Helmet>
23+
<title>{pageTitle("Git Authentication Settings")}</title>
24+
</Helmet>
25+
26+
<Header
27+
title="Git Authentication"
28+
description="Coder integrates with GitHub, GitLab, BitBucket, and Azure Repos to authenticate developers with your Git provider."
29+
docsHref="https://coder.com/docs/coder-oss/latest/admin/git"
30+
/>
31+
32+
<video
33+
autoPlay
34+
muted
35+
loop
36+
playsInline
37+
src="/gitauth.mp4"
38+
style={{
39+
maxWidth: "100%",
40+
borderRadius: 4,
41+
}}
42+
/>
43+
44+
<div className={styles.description}>
45+
<AlertBanner
46+
severity="info"
47+
text="Integrating with multiple Git providers is an Enterprise feature."
48+
actions={[<EnterpriseBadge key="enterprise" />]}
49+
/>
50+
</div>
51+
52+
<TableContainer>
53+
<Table className={styles.table}>
54+
<TableHead>
55+
<TableRow>
56+
<TableCell width="25%">Type</TableCell>
57+
<TableCell width="25%">Client ID</TableCell>
58+
<TableCell width="25%">Match</TableCell>
59+
</TableRow>
60+
</TableHead>
61+
<TableBody>
62+
{deploymentConfig.gitauth.value.length === 0 && (
63+
<TableRow>
64+
<TableCell colSpan={999}>
65+
<div className={styles.empty}>
66+
No providers have been configured!
67+
</div>
68+
</TableCell>
69+
</TableRow>
70+
)}
71+
72+
{deploymentConfig.gitauth.value.map((git) => {
73+
const name = git.id || git.type
74+
return (
75+
<TableRow key={name}>
76+
<TableCell>{name}</TableCell>
77+
<TableCell>{git.client_id}</TableCell>
78+
<TableCell>{git.regex || "Not Set"}</TableCell>
79+
</TableRow>
80+
)
81+
})}
82+
</TableBody>
83+
</Table>
84+
</TableContainer>
85+
</>
86+
)
87+
}
88+
89+
const useStyles = makeStyles((theme) => ({
90+
table: {
91+
"& td": {
92+
paddingTop: theme.spacing(3),
93+
paddingBottom: theme.spacing(3),
94+
},
95+
96+
"& td:last-child, & th:last-child": {
97+
paddingLeft: theme.spacing(4),
98+
},
99+
},
100+
description: {
101+
marginTop: theme.spacing(3),
102+
marginBottom: theme.spacing(3),
103+
},
104+
empty: {
105+
textAlign: "center",
106+
},
107+
}))
108+
109+
export default GitAuthSettingsPage

site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx renamed to site/src/pages/DeploySettingsPage/UserAuthSettingsPage.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ import React from "react"
1111
import { Helmet } from "react-helmet-async"
1212
import { pageTitle } from "util/page"
1313

14-
const AuthSettingsPage: React.FC = () => {
14+
const UserAuthSettingsPage: React.FC = () => {
1515
const { deploymentConfig: deploymentConfig } = useDeploySettings()
1616

1717
return (
1818
<>
1919
<Helmet>
20-
<title>{pageTitle("Authentication Settings")}</title>
20+
<title>{pageTitle("User Authentication Settings")}</title>
2121
</Helmet>
2222

2323
<Stack direction="column" spacing={6}>
2424
<div>
25-
<Header title="Authentication" />
25+
<Header title="User Authentication" />
2626

2727
<Header
2828
title="Login with OpenID Connect"
@@ -85,4 +85,4 @@ const AuthSettingsPage: React.FC = () => {
8585
)
8686
}
8787

88-
export default AuthSettingsPage
88+
export default UserAuthSettingsPage

site/static/gitauth.mp4

196 KB
Binary file not shown.

0 commit comments

Comments
 (0)