1diff --git a/pkg/handler/auth/login.go b/pkg/handler/auth/login.go
2new file mode 100644
3index 0000000000000000000000000000000000000000..7e77a67ba295d77bb0b742eb3ccefea98d9fd411
4--- /dev/null
5+++ b/pkg/handler/auth/login.go
6@@ -0,0 +1,20 @@
7+package auth
8+
9+import (
10+ "net/http"
11+
12+ "git.gabrielgio.me/cerrado/pkg/ext"
13+ "git.gabrielgio.me/cerrado/templates"
14+)
15+
16+type (
17+ LoginHandler struct{}
18+)
19+
20+func (g *LoginHandler) Login(w http.ResponseWriter, r *http.Request) error {
21+ ext.SetHTML(w)
22+
23+ login := &templates.LoginPage{}
24+ templates.WritePageTemplate(w, login)
25+ return nil
26+}
27diff --git a/pkg/handler/router.go b/pkg/handler/router.go
28index a2ac98a151f99376795efb2ad8a5b2268f35e578..32bd78adff98f82bca843aba96d2ddb85d7c79f7 100644
29--- a/pkg/handler/router.go
30+++ b/pkg/handler/router.go
31@@ -6,6 +6,7 @@
32 serverconfig "git.gabrielgio.me/cerrado/pkg/config"
33 "git.gabrielgio.me/cerrado/pkg/ext"
34 "git.gabrielgio.me/cerrado/pkg/handler/about"
35+ "git.gabrielgio.me/cerrado/pkg/handler/auth"
36 "git.gabrielgio.me/cerrado/pkg/handler/git"
37 "git.gabrielgio.me/cerrado/pkg/handler/static"
38 "git.gabrielgio.me/cerrado/pkg/service"
39@@ -21,6 +22,7 @@ ) (http.Handler, error) {
40 var (
41 gitHandler = git.NewGitHandler(gitService, configRepo)
42 aboutHandler = about.NewAboutHandler(configRepo)
43+ loginHandler = &auth.LoginHandler{}
44 )
45
46 staticHandler, err := static.ServeStaticHandler()
47@@ -33,6 +35,7 @@ mux.AddMiddleware(ext.Compress)
48 mux.AddMiddleware(ext.Log)
49
50 mux.HandleFunc("/static/{file}", staticHandler)
51+ mux.HandleFunc("/login/{$}", loginHandler.Login)
52 mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
53 mux.HandleFunc("/{name}/", gitHandler.Summary)
54 mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
55diff --git a/scss/main.scss b/scss/main.scss
56index b76423f843be94eb52197bf2e42b219eada6c556..21701f5408920e4d33ab77f61ccd496064469e36 100644
57--- a/scss/main.scss
58+++ b/scss/main.scss
59@@ -5,6 +5,10 @@ $base-font-size: 1rem;
60 $font-family-monospace: monospace;
61 $headings-margin-bottom: 0;
62
63+$input-border-radius: 0;
64+
65+$btn-border-radius: 0;
66+
67 // basic functionality
68 @import "bootstrap/scss/_functions.scss";
69 @import "bootstrap/scss/_variables.scss";
70@@ -21,6 +25,8 @@ @import "bootstrap/scss/_containers.scss";
71 @import "bootstrap/scss/_nav.scss";
72 @import "bootstrap/scss/_navbar.scss";
73 @import "bootstrap/scss/_grid.scss";
74+@import "bootstrap/scss/_forms.scss";
75+@import "bootstrap/scss/_buttons.scss";
76 @import "tree.scss";
77
78 // TODO remove once is not needed
79@@ -80,7 +86,6 @@ property: margin-left,
80 class: ms,
81 values: map-merge($spacers, (auto: auto))
82 ),
83-
84 );
85
86 @import "bootstrap/scss/utilities/_api.scss";
87@@ -162,3 +167,10 @@ padding: $spacer;
88 max-width: calc(100% - calc(2 * #{$spacer}));
89 }
90 }
91+
92+input {
93+ width: 100%;
94+ box-sizing: border-box;
95+ -webkit-box-sizing:border-box;
96+ -moz-box-sizing: border-box;
97+}
98diff --git a/templates/login.qtpl b/templates/login.qtpl
99new file mode 100644
100index 0000000000000000000000000000000000000000..eee57117106360c2f0d9bbf104e0b820bf051e92
101--- /dev/null
102+++ b/templates/login.qtpl
103@@ -0,0 +1,31 @@
104+{% code
105+type LoginPage struct {
106+}
107+%}
108+
109+{% func (p *LoginPage) Title() %}Hello{% endfunc %}
110+
111+{% func (p *LoginPage) Navbar() %}{%= Navbar(Login) %}{% endfunc %}
112+
113+{% func (p *LoginPage) Content() %}
114+<div class="row">
115+ <div class="col-md-6 offset-md-3">
116+ <form>
117+ <div class="form-group m-3">
118+ <label for="username" class="form-label">Username</label>
119+ <input type="text" class="form-control" id="username" aria-describedby="emailHelp">
120+ </div>
121+ <div class="form-group m-3">
122+ <label for="password" class="form-label">Password</label>
123+ <input type="password" class="form-control" id="password">
124+ </div>
125+ <div class="form-group m-3">
126+ <button type="submit" class="btn btn-primary">Login</button>
127+ </div>
128+ </form>
129+ </div>
130+</div>
131+{% endfunc %}
132+
133+{% func (p *LoginPage) Script() %}
134+{% endfunc %}
135diff --git a/templates/login.qtpl.go b/templates/login.qtpl.go
136new file mode 100644
137index 0000000000000000000000000000000000000000..0d3d2b0c41d592cf030a0806131d1057e8726786
138--- /dev/null
139+++ b/templates/login.qtpl.go
140@@ -0,0 +1,173 @@
141+// Code generated by qtc from "login.qtpl". DO NOT EDIT.
142+// See https://github.com/valyala/quicktemplate for details.
143+
144+//line templates/login.qtpl:1
145+package templates
146+
147+//line templates/login.qtpl:1
148+import (
149+ qtio422016 "io"
150+
151+ qt422016 "github.com/valyala/quicktemplate"
152+)
153+
154+//line templates/login.qtpl:1
155+var (
156+ _ = qtio422016.Copy
157+ _ = qt422016.AcquireByteBuffer
158+)
159+
160+//line templates/login.qtpl:2
161+type LoginPage struct {
162+}
163+
164+//line templates/login.qtpl:6
165+func (p *LoginPage) StreamTitle(qw422016 *qt422016.Writer) {
166+//line templates/login.qtpl:6
167+ qw422016.N().S(`Hello`)
168+//line templates/login.qtpl:6
169+}
170+
171+//line templates/login.qtpl:6
172+func (p *LoginPage) WriteTitle(qq422016 qtio422016.Writer) {
173+//line templates/login.qtpl:6
174+ qw422016 := qt422016.AcquireWriter(qq422016)
175+//line templates/login.qtpl:6
176+ p.StreamTitle(qw422016)
177+//line templates/login.qtpl:6
178+ qt422016.ReleaseWriter(qw422016)
179+//line templates/login.qtpl:6
180+}
181+
182+//line templates/login.qtpl:6
183+func (p *LoginPage) Title() string {
184+//line templates/login.qtpl:6
185+ qb422016 := qt422016.AcquireByteBuffer()
186+//line templates/login.qtpl:6
187+ p.WriteTitle(qb422016)
188+//line templates/login.qtpl:6
189+ qs422016 := string(qb422016.B)
190+//line templates/login.qtpl:6
191+ qt422016.ReleaseByteBuffer(qb422016)
192+//line templates/login.qtpl:6
193+ return qs422016
194+//line templates/login.qtpl:6
195+}
196+
197+//line templates/login.qtpl:8
198+func (p *LoginPage) StreamNavbar(qw422016 *qt422016.Writer) {
199+//line templates/login.qtpl:8
200+ StreamNavbar(qw422016, Login)
201+//line templates/login.qtpl:8
202+}
203+
204+//line templates/login.qtpl:8
205+func (p *LoginPage) WriteNavbar(qq422016 qtio422016.Writer) {
206+//line templates/login.qtpl:8
207+ qw422016 := qt422016.AcquireWriter(qq422016)
208+//line templates/login.qtpl:8
209+ p.StreamNavbar(qw422016)
210+//line templates/login.qtpl:8
211+ qt422016.ReleaseWriter(qw422016)
212+//line templates/login.qtpl:8
213+}
214+
215+//line templates/login.qtpl:8
216+func (p *LoginPage) Navbar() string {
217+//line templates/login.qtpl:8
218+ qb422016 := qt422016.AcquireByteBuffer()
219+//line templates/login.qtpl:8
220+ p.WriteNavbar(qb422016)
221+//line templates/login.qtpl:8
222+ qs422016 := string(qb422016.B)
223+//line templates/login.qtpl:8
224+ qt422016.ReleaseByteBuffer(qb422016)
225+//line templates/login.qtpl:8
226+ return qs422016
227+//line templates/login.qtpl:8
228+}
229+
230+//line templates/login.qtpl:10
231+func (p *LoginPage) StreamContent(qw422016 *qt422016.Writer) {
232+//line templates/login.qtpl:10
233+ qw422016.N().S(`
234+<div class="row">
235+ <div class="col-md-6 offset-md-3">
236+ <form>
237+ <div class="form-group m-3">
238+ <label for="username" class="form-label">Username</label>
239+ <input type="text" class="form-control" id="username" aria-describedby="emailHelp">
240+ </div>
241+ <div class="form-group m-3">
242+ <label for="password" class="form-label">Password</label>
243+ <input type="password" class="form-control" id="password">
244+ </div>
245+ <div class="form-group m-3">
246+ <button type="submit" class="btn btn-primary">Login</button>
247+ </div>
248+ </form>
249+ </div>
250+</div>
251+`)
252+//line templates/login.qtpl:28
253+}
254+
255+//line templates/login.qtpl:28
256+func (p *LoginPage) WriteContent(qq422016 qtio422016.Writer) {
257+//line templates/login.qtpl:28
258+ qw422016 := qt422016.AcquireWriter(qq422016)
259+//line templates/login.qtpl:28
260+ p.StreamContent(qw422016)
261+//line templates/login.qtpl:28
262+ qt422016.ReleaseWriter(qw422016)
263+//line templates/login.qtpl:28
264+}
265+
266+//line templates/login.qtpl:28
267+func (p *LoginPage) Content() string {
268+//line templates/login.qtpl:28
269+ qb422016 := qt422016.AcquireByteBuffer()
270+//line templates/login.qtpl:28
271+ p.WriteContent(qb422016)
272+//line templates/login.qtpl:28
273+ qs422016 := string(qb422016.B)
274+//line templates/login.qtpl:28
275+ qt422016.ReleaseByteBuffer(qb422016)
276+//line templates/login.qtpl:28
277+ return qs422016
278+//line templates/login.qtpl:28
279+}
280+
281+//line templates/login.qtpl:30
282+func (p *LoginPage) StreamScript(qw422016 *qt422016.Writer) {
283+//line templates/login.qtpl:30
284+ qw422016.N().S(`
285+`)
286+//line templates/login.qtpl:31
287+}
288+
289+//line templates/login.qtpl:31
290+func (p *LoginPage) WriteScript(qq422016 qtio422016.Writer) {
291+//line templates/login.qtpl:31
292+ qw422016 := qt422016.AcquireWriter(qq422016)
293+//line templates/login.qtpl:31
294+ p.StreamScript(qw422016)
295+//line templates/login.qtpl:31
296+ qt422016.ReleaseWriter(qw422016)
297+//line templates/login.qtpl:31
298+}
299+
300+//line templates/login.qtpl:31
301+func (p *LoginPage) Script() string {
302+//line templates/login.qtpl:31
303+ qb422016 := qt422016.AcquireByteBuffer()
304+//line templates/login.qtpl:31
305+ p.WriteScript(qb422016)
306+//line templates/login.qtpl:31
307+ qs422016 := string(qb422016.B)
308+//line templates/login.qtpl:31
309+ qt422016.ReleaseByteBuffer(qb422016)
310+//line templates/login.qtpl:31
311+ return qs422016
312+//line templates/login.qtpl:31
313+}
314diff --git a/templates/navbar.qtpl b/templates/navbar.qtpl
315index 8f5f9e135c7cc960e0b529f044e972f792ad95fb..62de2196f4367c47b805d6a25835a03fed55332c 100644
316--- a/templates/navbar.qtpl
317+++ b/templates/navbar.qtpl
318@@ -5,6 +5,7 @@ Git Selection = iota
319 List
320 About
321 Config
322+ Login
323 )
324 %}
325
326@@ -16,7 +17,6 @@ Log
327 Summary
328 Refs
329 Tree
330- Login
331 )
332 %}
333
334diff --git a/templates/navbar.qtpl.go b/templates/navbar.qtpl.go
335index e6ca0aa2783df2afe1e88a57186f46649b0986a9..ad857f78a8c07825fa29201131580cfb30f6444b 100644
336--- a/templates/navbar.qtpl.go
337+++ b/templates/navbar.qtpl.go
338@@ -25,9 +25,10 @@ Git Selection = iota
339 List
340 About
341 Config
342+ Login
343 )
344
345-//line templates/navbar.qtpl:12
346+//line templates/navbar.qtpl:13
347 type GitSelection int
348
349 const (
350@@ -36,7 +37,6 @@ Log
351 Summary
352 Refs
353 Tree
354- Login
355 )
356
357 //line templates/navbar.qtpl:23
358@@ -93,7 +93,7 @@ <a class="nav-link`)
359 //line templates/navbar.qtpl:31
360 streaminsertIfEqual(qw422016, s, Login)
361 //line templates/navbar.qtpl:31
362- qw422016.N().S(`" href="/">login</a>
363+ qw422016.N().S(`" href="/login">login</a>
364 `)
365 //line templates/navbar.qtpl:35
366 qw422016.N().S(`