+5
-1
cmd/legit/main.go
+5
-1
cmd/legit/main.go
···
3
import (
4
"flag"
5
"fmt"
6
"log"
7
"net/http"
8
9
"github.com/icyphox/bild/legit/config"
10
"github.com/icyphox/bild/legit/routes"
···
20
log.Fatal(err)
21
}
22
23
-
mux := routes.Handlers(c)
24
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
25
log.Println("starting server on", addr)
26
log.Fatal(http.ListenAndServe(addr, mux))
···
3
import (
4
"flag"
5
"fmt"
6
+
"html/template"
7
"log"
8
"net/http"
9
+
"path/filepath"
10
11
"github.com/icyphox/bild/legit/config"
12
"github.com/icyphox/bild/legit/routes"
···
22
log.Fatal(err)
23
}
24
25
+
t := template.Must(template.ParseGlob(filepath.Join(c.Dirs.Templates, "*")))
26
+
27
+
mux := routes.Handlers(c, t)
28
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
29
log.Println("starting server on", addr)
30
log.Fatal(http.ListenAndServe(addr, mux))
+30
-24
flake.nix
+30
-24
flake.nix
···
15
};
16
};
17
18
-
outputs = {
19
-
self,
20
-
nixpkgs,
21
-
gitignore,
22
-
rust-overlay,
23
-
}: let
24
-
supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
25
-
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
26
-
nixpkgsFor = forAllSystems (system:
27
-
import nixpkgs {
28
-
inherit system;
29
-
overlays = [(import rust-overlay)];
30
-
});
31
-
in {
32
-
defaultPackage = forAllSystems (system: self.packages.${system}.legit);
33
-
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
34
-
devShells = forAllSystems (system: let
35
-
pkgs = nixpkgsFor.${system};
36
-
rust-bin = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
37
-
in {
38
-
default = pkgs.mkShell {
39
-
nativeBuildInputs = [
40
-
pkgs.go
41
-
pkgs.air
42
43
pkgs.httpie
44
pkgs.bacon
···
15
};
16
};
17
18
+
outputs =
19
+
{ self
20
+
, nixpkgs
21
+
, gitignore
22
+
, rust-overlay
23
+
,
24
+
}:
25
+
let
26
+
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
27
+
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
28
+
nixpkgsFor = forAllSystems (system:
29
+
import nixpkgs {
30
+
inherit system;
31
+
overlays = [ (import rust-overlay) ];
32
+
});
33
+
in
34
+
{
35
+
defaultPackage = forAllSystems (system: self.packages.${system}.legit);
36
+
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
37
+
devShells = forAllSystems (system:
38
+
let
39
+
pkgs = nixpkgsFor.${system};
40
+
rust-bin = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
41
+
in
42
+
{
43
+
default = pkgs.mkShell {
44
+
nativeBuildInputs = [
45
+
pkgs.go
46
+
pkgs.air
47
+
pkgs.templ
48
49
pkgs.httpie
50
pkgs.bacon
+6
-4
go.mod
+6
-4
go.mod
···
1
module github.com/icyphox/bild
2
3
-
go 1.22.0
4
5
require (
6
github.com/alecthomas/chroma/v2 v2.14.0
···
33
github.com/sergi/go-diff v1.3.1 // indirect
34
github.com/skeema/knownhosts v1.3.0 // indirect
35
github.com/xanzy/ssh-agent v0.3.3 // indirect
36
-
golang.org/x/crypto v0.28.0 // indirect
37
-
golang.org/x/net v0.30.0 // indirect
38
-
golang.org/x/sys v0.26.0 // indirect
39
gopkg.in/warnings.v0 v0.1.2 // indirect
40
)
41
···
1
module github.com/icyphox/bild
2
3
+
go 1.23
4
+
5
+
toolchain go1.23.4
6
7
require (
8
github.com/alecthomas/chroma/v2 v2.14.0
···
35
github.com/sergi/go-diff v1.3.1 // indirect
36
github.com/skeema/knownhosts v1.3.0 // indirect
37
github.com/xanzy/ssh-agent v0.3.3 // indirect
38
+
golang.org/x/crypto v0.31.0 // indirect
39
+
golang.org/x/net v0.33.0 // indirect
40
+
golang.org/x/sys v0.28.0 // indirect
41
gopkg.in/warnings.v0 v0.1.2 // indirect
42
)
43
+10
-10
go.sum
+10
-10
go.sum
···
119
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
120
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
121
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
122
-
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
123
-
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
124
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
125
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
126
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
···
134
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
135
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
136
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
137
-
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
138
-
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
139
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
140
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
141
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
158
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
159
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
160
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
161
-
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
162
-
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
163
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
164
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
165
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
···
167
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
168
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
169
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
170
-
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
171
-
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
172
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
173
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
174
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
···
176
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
177
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
178
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
179
-
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
180
-
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
181
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
182
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
183
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
···
119
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
120
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
121
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
122
+
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
123
+
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
124
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
125
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
126
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
···
134
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
135
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
136
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
137
+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
138
+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
139
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
140
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
141
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
158
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
159
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
160
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
161
+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
162
+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
163
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
164
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
165
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
···
167
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
168
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
169
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
170
+
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
171
+
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
172
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
173
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
174
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
···
176
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
177
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
178
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
179
+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
180
+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
181
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
182
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
183
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+4
-2
legit/routes/handler.go
+4
-2
legit/routes/handler.go
···
1
package routes
2
3
import (
4
+
"html/template"
5
"net/http"
6
7
"github.com/go-chi/chi/v5"
···
30
}
31
}
32
33
+
func Handlers(c *config.Config, t *template.Template) http.Handler {
34
r := chi.NewRouter()
35
+
d := deps{c, t}
36
37
+
r.Get("/login", d.Login)
38
r.Get("/static/{file}", d.ServeStatic)
39
40
r.Route("/@{user}", func(r chi.Router) {
+13
-20
legit/routes/routes.go
+13
-20
legit/routes/routes.go
···
22
23
type deps struct {
24
c *config.Config
25
}
26
27
func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
···
73
return infos[j].d.Before(infos[i].d)
74
})
75
76
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
77
-
t := template.Must(template.ParseGlob(tpath))
78
-
79
data := make(map[string]interface{})
80
data["meta"] = d.c.Meta
81
data["info"] = infos
82
83
-
if err := t.ExecuteTemplate(w, "index", data); err != nil {
84
log.Println(err)
85
return
86
}
···
142
log.Println(err)
143
return
144
}
145
-
146
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
147
-
t := template.Must(template.ParseGlob(tpath))
148
149
if len(commits) >= 3 {
150
commits = commits[:3]
···
161
data["meta"] = d.c.Meta
162
data["gomod"] = isGoModule(gr)
163
164
-
if err := t.ExecuteTemplate(w, "repo", data); err != nil {
165
log.Println(err)
166
return
167
}
···
327
return
328
}
329
330
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
331
-
t := template.Must(template.ParseGlob(tpath))
332
-
333
data := make(map[string]interface{})
334
data["commits"] = commits
335
data["meta"] = d.c.Meta
···
339
data["desc"] = getDescription(path)
340
data["log"] = true
341
342
-
if err := t.ExecuteTemplate(w, "log", data); err != nil {
343
log.Println(err)
344
return
345
}
···
367
return
368
}
369
370
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
371
-
t := template.Must(template.ParseGlob(tpath))
372
-
373
data := make(map[string]interface{})
374
375
data["commit"] = diff.Commit
···
381
data["ref"] = ref
382
data["desc"] = getDescription(path)
383
384
-
if err := t.ExecuteTemplate(w, "commit", data); err != nil {
385
log.Println(err)
386
return
387
}
···
414
return
415
}
416
417
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
418
-
t := template.Must(template.ParseGlob(tpath))
419
-
420
data := make(map[string]interface{})
421
422
data["meta"] = d.c.Meta
···
426
data["tags"] = tags
427
data["desc"] = getDescription(path)
428
429
-
if err := t.ExecuteTemplate(w, "refs", data); err != nil {
430
log.Println(err)
431
return
432
}
···
438
439
http.ServeFile(w, r, f)
440
}
···
22
23
type deps struct {
24
c *config.Config
25
+
t *template.Template
26
}
27
28
func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
···
74
return infos[j].d.Before(infos[i].d)
75
})
76
77
data := make(map[string]interface{})
78
data["meta"] = d.c.Meta
79
data["info"] = infos
80
81
+
if err := d.t.ExecuteTemplate(w, "index", data); err != nil {
82
log.Println(err)
83
return
84
}
···
140
log.Println(err)
141
return
142
}
143
144
if len(commits) >= 3 {
145
commits = commits[:3]
···
156
data["meta"] = d.c.Meta
157
data["gomod"] = isGoModule(gr)
158
159
+
if err := d.t.ExecuteTemplate(w, "repo", data); err != nil {
160
log.Println(err)
161
return
162
}
···
322
return
323
}
324
325
data := make(map[string]interface{})
326
data["commits"] = commits
327
data["meta"] = d.c.Meta
···
331
data["desc"] = getDescription(path)
332
data["log"] = true
333
334
+
if err := d.t.ExecuteTemplate(w, "log", data); err != nil {
335
log.Println(err)
336
return
337
}
···
359
return
360
}
361
362
data := make(map[string]interface{})
363
364
data["commit"] = diff.Commit
···
370
data["ref"] = ref
371
data["desc"] = getDescription(path)
372
373
+
if err := d.t.ExecuteTemplate(w, "commit", data); err != nil {
374
log.Println(err)
375
return
376
}
···
403
return
404
}
405
406
data := make(map[string]interface{})
407
408
data["meta"] = d.c.Meta
···
412
data["tags"] = tags
413
data["desc"] = getDescription(path)
414
415
+
if err := d.t.ExecuteTemplate(w, "refs", data); err != nil {
416
log.Println(err)
417
return
418
}
···
424
425
http.ServeFile(w, r, f)
426
}
427
+
428
+
func (d *deps) Login(w http.ResponseWriter, r *http.Request) {
429
+
if err := d.t.ExecuteTemplate(w, "login", nil); err != nil {
430
+
log.Println(err)
431
+
return
432
+
}
433
+
}
+29
legit/templates/login.html
+29
legit/templates/login.html
···
···
1
+
{{ define "login" }}
2
+
<html>
3
+
{{ template "head" . }}
4
+
5
+
<body>
6
+
<main>
7
+
<form
8
+
class="form-login"
9
+
method="post"
10
+
action="http://localhost:3000/login"
11
+
>
12
+
<p>
13
+
You will be redirected to bsky.app (or your PDS) to complete
14
+
login.
15
+
</p>
16
+
<div>
17
+
<input
18
+
type="text"
19
+
id="handle"
20
+
name="handle"
21
+
placeholder="@username.bsky.social"
22
+
/>
23
+
</div>
24
+
<button type="submit">Login</button>
25
+
</form>
26
+
</main>
27
+
</body>
28
+
</html>
29
+
{{ end }}