Skip to content

Commit 6aec3a1

Browse files
authored
Merge branch 'main' into cli
2 parents aed4a62 + 277318b commit 6aec3a1

File tree

16 files changed

+1060
-577
lines changed

16 files changed

+1060
-577
lines changed

.github/stale.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
daysUntilStale: 14
33
# Number of days of inactivity before a stale issue is closed
44
daysUntilClose: 5
5+
# Only apply the stale logic to pulls, since we are using issues to manage work
6+
only: pulls
57
# Label to apply when stale.
68
staleLabel: stale
79
# Comment to post when marking an issue as stale. Set to `false` to disable

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ provisionersdk/proto: provisionersdk/proto/provisioner.proto
7979
.PHONY: provisionersdk/proto
8080

8181
site/out:
82+
cd site && yarn install
8283
cd site && yarn build
8384
cd site && yarn export
8485
.PHONY: site/out

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@ This repository contains source code for Coder V2. Additional documentation:
1313
- [`semantic.yaml`](./github/semantic.yaml): Configuration for [semantic pull requests](https://github.com/apps/semantic-pull-requests)
1414
- `site`: Front-end UI code.
1515

16+
## Development
17+
18+
### Cloning
19+
20+
- `git clone https://github.com/coder/coder`
21+
- `cd coder`
22+
23+
### Building
24+
25+
- `make build`
26+
27+
### Development
28+
29+
- `./develop.sh`
30+
31+
The `develop.sh` script runs the server locally on port `3000`, and runs a hot-reload server for front-end code on `8080`.
32+
1633
## Front-End Plan
1734

1835
For the front-end team, we're planning on 2 phases to the 'v2' work:

coderd/coderd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func New(options *Options) http.Handler {
127127
})
128128
})
129129
})
130-
r.NotFound(site.Handler().ServeHTTP)
130+
r.NotFound(site.Handler(options.Logger).ServeHTTP)
131131
return r
132132
}
133133

develop.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ function create_initial_user() {
2121
http://localhost:3000/api/v2/user
2222
}
2323

24+
# Run yarn install, to make sure node_modules are ready to go
25+
yarn --cwd=./site install
26+
2427
# Do initial build - a dev build for coderd.
2528
# It's OK that we don't build the front-end before - because the front-end
2629
# assets are handled by the `yarn dev` devserver.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
github.com/pion/logging v0.2.2
3838
github.com/pion/transport v0.13.0
3939
github.com/pion/webrtc/v3 v3.1.23
40+
github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef
4041
github.com/quasilyte/go-ruleguard/dsl v0.3.16
4142
github.com/spf13/cobra v1.3.0
4243
github.com/stretchr/testify v1.7.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
11041104
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
11051105
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
11061106
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
1107+
github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef h1:NKxTG6GVGbfMXc2mIk+KphcH6hagbVXhcFkbTgYleTI=
1108+
github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI=
11071109
github.com/quasilyte/go-ruleguard/dsl v0.3.16 h1:yJtIpd4oyNS+/c/gKqxNwoGO9+lPOsy1A4BzKjJRcrI=
11081110
github.com/quasilyte/go-ruleguard/dsl v0.3.16/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
11091111
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Story } from "@storybook/react"
2+
import React from "react"
3+
import { SignInForm, SignInProps } from "./SignInForm"
4+
5+
export default {
6+
title: "SignIn/SignInForm",
7+
component: SignInForm,
8+
argTypes: {
9+
loginHandler: { action: "Login" },
10+
},
11+
}
12+
13+
const Template: Story<SignInProps> = (args) => <SignInForm {...args} />
14+
15+
export const Example = Template.bind({})
16+
Example.args = {}

site/components/SignIn/SignInForm.tsx

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,33 +31,8 @@ const useStyles = makeStyles((theme) => ({
3131
borderTop: `1px solid ${theme.palette.action.disabled}`,
3232
paddingTop: theme.spacing(3),
3333
},
34-
loginTypeToggleWrapper: {
34+
loginTextField: {
3535
marginTop: theme.spacing(2),
36-
display: "flex",
37-
justifyContent: "center",
38-
},
39-
loginTypeToggleBtn: {
40-
color: theme.palette.text.primary,
41-
// We want opacity so that this isn't super highlighted for the user.
42-
// In most cases, they shouldn't want to switch login types.
43-
opacity: 0.5,
44-
"&:hover": {
45-
cursor: "pointer",
46-
opacity: 1,
47-
textDecoration: "underline",
48-
},
49-
},
50-
loginTypeToggleBtnFocusVisible: {
51-
opacity: 1,
52-
textDecoration: "underline",
53-
},
54-
loginTypeBtn: {
55-
backgroundColor: "#2A2B45",
56-
textTransform: "none",
57-
58-
"&:not(:first-child)": {
59-
marginTop: theme.spacing(2),
60-
},
6136
},
6237
submitBtn: {
6338
marginTop: theme.spacing(2),
@@ -101,27 +76,27 @@ export const SignInForm: React.FC<SignInProps> = ({
10176
<FormTextField
10277
autoComplete="email"
10378
autoFocus
79+
className={styles.loginTextField}
10480
eventTransform={(email: string) => email.trim()}
10581
form={form}
10682
formFieldName="email"
10783
fullWidth
10884
inputProps={{
10985
id: "signin-form-inpt-email",
11086
}}
111-
margin="none"
11287
placeholder="Email"
11388
variant="outlined"
11489
/>
11590
<FormTextField
11691
autoComplete="current-password"
92+
className={styles.loginTextField}
11793
form={form}
11894
formFieldName="password"
11995
fullWidth
12096
inputProps={{
12197
id: "signin-form-inpt-password",
12298
}}
12399
isPassword
124-
margin="none"
125100
placeholder="Password"
126101
variant="outlined"
127102
/>

site/embed.go

Lines changed: 21 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
package site
22

33
import (
4-
"bytes"
54
"embed"
65
"fmt"
7-
"io"
86
"io/fs"
97
"net/http"
10-
"path"
11-
"path/filepath"
128
"strings"
13-
"text/template" // html/template escapes some nonces
14-
"time"
159

1610
"github.com/justinas/nosurf"
1711
"github.com/unrolled/secure"
18-
"golang.org/x/xerrors"
12+
13+
"cdr.dev/slog"
14+
15+
"github.com/coder/coder/site/nextrouter"
1916
)
2017

2118
// The `embed` package ignores recursively including directories
@@ -27,53 +24,33 @@ import (
2724
var site embed.FS
2825

2926
// Handler returns an HTTP handler for serving the static site.
30-
func Handler() http.Handler {
27+
func Handler(logger slog.Logger) http.Handler {
3128
filesystem, err := fs.Sub(site, "out")
3229
if err != nil {
3330
// This can't happen... Go would throw a compilation error.
3431
panic(err)
3532
}
3633

37-
// html files are handled by a text/template. Non-html files
38-
// are served by the default file server.
39-
files, err := htmlFiles(filesystem)
40-
if err != nil {
41-
panic(xerrors.Errorf("Failed to return handler for static files. Html files failed to load: %w", err))
34+
// Render CSP and CSRF in the served pages
35+
templateFunc := func(r *http.Request) interface{} {
36+
return htmlState{
37+
// Nonce is the CSP nonce for the given request (if there is one present)
38+
CSP: cspState{Nonce: secure.CSPNonce(r.Context())},
39+
// Token is the CSRF token for the given request
40+
CSRF: csrfState{Token: nosurf.Token(r)},
41+
}
4242
}
4343

44-
return secureHeaders(&handler{
45-
fs: filesystem,
46-
htmlFiles: files,
47-
h: http.FileServer(http.FS(filesystem)), // All other non-html static files
44+
nextRouterHandler, err := nextrouter.Handler(filesystem, &nextrouter.Options{
45+
Logger: logger,
46+
TemplateDataFunc: templateFunc,
4847
})
49-
}
50-
51-
type handler struct {
52-
fs fs.FS
53-
// htmlFiles is the text/template for all *.html files.
54-
// This is needed to support Content Security Policy headers.
55-
// Due to material UI, we are forced to use a nonce to allow inline
56-
// scripts, and that nonce is passed through a template.
57-
// We only do this for html files to reduce the amount of in memory caching
58-
// of duplicate files as `fs`.
59-
htmlFiles *htmlTemplates
60-
h http.Handler
61-
}
62-
63-
// filePath returns the filepath of the requested file.
64-
func (*handler) filePath(p string) string {
65-
if !strings.HasPrefix(p, "/") {
66-
p = "/" + p
67-
}
68-
return strings.TrimPrefix(path.Clean(p), "/")
69-
}
70-
71-
func (h *handler) exists(filePath string) bool {
72-
f, err := h.fs.Open(filePath)
73-
if err == nil {
74-
_ = f.Close()
48+
if err != nil {
49+
// There was an error setting up our file system handler.
50+
// This likely means a problem with our embedded file system.
51+
panic(err)
7552
}
76-
return err == nil
53+
return secureHeaders(nextRouterHandler)
7754
}
7855

7956
type htmlState struct {
@@ -89,80 +66,6 @@ type csrfState struct {
8966
Token string
9067
}
9168

92-
func (h *handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
93-
// reqFile is the static file requested
94-
reqFile := h.filePath(r.URL.Path)
95-
state := htmlState{
96-
// Nonce is the CSP nonce for the given request (if there is one present)
97-
CSP: cspState{Nonce: secure.CSPNonce(r.Context())},
98-
// Token is the CSRF token for the given request
99-
CSRF: csrfState{Token: nosurf.Token(r)},
100-
}
101-
102-
// First check if it's a file we have in our templates
103-
if h.serveHTML(rw, r, reqFile, state) {
104-
return
105-
}
106-
107-
// If the original file path exists we serve it.
108-
if h.exists(reqFile) {
109-
h.h.ServeHTTP(rw, r)
110-
return
111-
}
112-
113-
// Serve the file assuming it's an html file
114-
// This matches paths like `/app/terminal.html`
115-
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
116-
r.URL.Path += ".html"
117-
118-
reqFile = h.filePath(r.URL.Path)
119-
// All html files should be served by the htmlFile templates
120-
if h.serveHTML(rw, r, reqFile, state) {
121-
return
122-
}
123-
124-
// If we don't have the file... we should redirect to `/`
125-
// for our single-page-app.
126-
r.URL.Path = "/"
127-
if h.serveHTML(rw, r, "", state) {
128-
return
129-
}
130-
131-
// This will send a correct 404
132-
h.h.ServeHTTP(rw, r)
133-
}
134-
135-
func (h *handler) serveHTML(rw http.ResponseWriter, r *http.Request, reqPath string, state htmlState) bool {
136-
if data, err := h.htmlFiles.renderWithState(reqPath, state); err == nil {
137-
if reqPath == "" {
138-
// Pass "index.html" to the ServeContent so the ServeContent sets the right content headers.
139-
reqPath = "index.html"
140-
}
141-
http.ServeContent(rw, r, reqPath, time.Time{}, bytes.NewReader(data))
142-
return true
143-
}
144-
return false
145-
}
146-
147-
type htmlTemplates struct {
148-
tpls *template.Template
149-
}
150-
151-
// renderWithState will render the file using the given nonce if the file exists
152-
// as a template. If it does not, it will return an error.
153-
func (t *htmlTemplates) renderWithState(filePath string, state htmlState) ([]byte, error) {
154-
var buf bytes.Buffer
155-
if filePath == "" {
156-
filePath = "index.html"
157-
}
158-
err := t.tpls.ExecuteTemplate(&buf, filePath, state)
159-
if err != nil {
160-
return nil, err
161-
}
162-
163-
return buf.Bytes(), nil
164-
}
165-
16669
// cspDirectives is a map of all csp fetch directives to their values.
16770
// Each directive is a set of values that is joined by a space (' ').
16871
// All directives are semi-colon separated as a single string for the csp header.
@@ -264,52 +167,3 @@ func secureHeaders(next http.Handler) http.Handler {
264167
ReferrerPolicy: "no-referrer",
265168
}).Handler(next)
266169
}
267-
268-
// htmlFiles recursively walks the file system passed finding all *.html files.
269-
// The template returned has all html files parsed.
270-
func htmlFiles(files fs.FS) (*htmlTemplates, error) {
271-
// root is the collection of html templates. All templates are named by their pathing.
272-
// So './404.html' is named '404.html'. './subdir/index.html' is 'subdir/index.html'
273-
root := template.New("")
274-
275-
rootPath := "."
276-
err := fs.WalkDir(files, rootPath, func(path string, dirEntry fs.DirEntry, err error) error {
277-
if err != nil {
278-
return err
279-
}
280-
281-
if dirEntry.IsDir() {
282-
return nil
283-
}
284-
285-
if filepath.Ext(dirEntry.Name()) != ".html" {
286-
return nil
287-
}
288-
289-
file, err := files.Open(path)
290-
if err != nil {
291-
return err
292-
}
293-
294-
data, err := io.ReadAll(file)
295-
if err != nil {
296-
return err
297-
}
298-
299-
tPath := strings.TrimPrefix(path, rootPath+string(filepath.Separator))
300-
_, err = root.New(tPath).Parse(string(data))
301-
if err != nil {
302-
return err
303-
}
304-
305-
return nil
306-
})
307-
308-
if err != nil {
309-
return nil, err
310-
}
311-
312-
return &htmlTemplates{
313-
tpls: root,
314-
}, nil
315-
}

0 commit comments

Comments
 (0)