landing.go

v1.3.6
Doc Versions Source
1
package server
2
3
import (
4
	"html/template"
5
	"log"
6
	"net/http"
7
)
8
9
var landingTmpl = template.Must(template.New("landing").Parse(`<!DOCTYPE html>
10
<html lang="en">
11
<head>
12
<meta charset="utf-8">
13
<meta name="viewport" content="width=device-width, initial-scale=1">
14
<title>{{.Host}} — Go Module Server</title>
15
<style>
16
:root{--bg:#fff;--bg2:#f8f9fa;--text:#202124;--text2:#5f6368;--border:#dadce0;--accent:#1a73e8;--green:#188038;--max:900px}
17
@media(prefers-color-scheme:dark){:root{--bg:#1e1e1e;--bg2:#252526;--text:#d4d4d4;--text2:#a0a0a0;--border:#404040;--accent:#6cb6ff;--green:#81c995}}
18
*{box-sizing:border-box;margin:0;padding:0}
19
body{font-family:system-ui,sans-serif;font-size:15px;line-height:1.6;color:var(--text);background:var(--bg)}
20
a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
21
.container{max-width:var(--max);margin:0 auto;padding:24px}
22
h1{font-size:28px;margin-bottom:4px}
23
.subtitle{color:var(--text2);font-size:16px;margin-bottom:32px}
24
h2{font-size:20px;margin:32px 0 12px}
25
pre{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:16px;font-family:monospace;font-size:13px;overflow-x:auto;margin-bottom:16px;line-height:1.5}
26
code{font-family:monospace;font-size:13px;background:var(--bg2);padding:2px 6px;border-radius:3px}
27
table{width:100%;border-collapse:collapse;margin-bottom:24px}
28
th{text-align:left;padding:8px 12px;border-bottom:2px solid var(--border);font-size:13px;color:var(--text2);text-transform:uppercase;letter-spacing:0.5px}
29
td{padding:8px 12px;border-bottom:1px solid var(--border);font-size:14px}
30
td:first-child{font-family:monospace;font-size:13px}
31
.note{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:12px 16px;font-size:14px;color:var(--text2);margin-top:16px}
32
footer{text-align:center;padding:32px 0 16px;font-size:12px;color:var(--text2)}
33
</style>
34
</head>
35
<body>
36
<div class="container">
37
<h1>{{.Host}}</h1>
38
<p class="subtitle">Vanity Go module server</p>
39
40
<h2>Setup</h2>
41
<p>Configure your Go toolchain to use this proxy:</p>
42
<pre>export GOPROXY=https://{{.Host}},direct{{if not .SumdbEnabled}}
43
export GONOSUMDB={{.Host}}{{end}}</pre>
44
45
{{if .SumdbEnabled}}
46
<p>This server provides a <a href="/sumdb/{{.Host}}/latest">transparency log</a> (sumdb) for module checksums.</p>
47
{{end}}
48
49
<p>Then import modules as usual:</p>
50
<pre>import "{{.Host}}/&lt;module-name&gt;"</pre>
51
52
{{if .Modules}}
53
<h2>Public Modules</h2>
54
<table>
55
<thead>
56
<tr><th>Module</th><th>Repository</th></tr>
57
</thead>
58
<tbody>
59
{{range .Modules}}
60
<tr>
61
<td><a href="/{{.Name}}">{{$.Host}}/{{.Name}}</a></td>
62
<td>{{if .Web}}<a href="{{.Web}}">{{.Web}}</a>{{else}}{{.Repo}}{{end}}</td>
63
</tr>
64
{{end}}
65
</tbody>
66
</table>
67
{{end}}
68
69
{{if .Patterns}}
70
<h2>Module Patterns</h2>
71
<p style="margin-bottom:12px;color:var(--text2);font-size:14px">Modules matching these patterns are served automatically.</p>
72
<table>
73
<thead>
74
<tr><th>Pattern</th><th>Repository Template</th></tr>
75
</thead>
76
<tbody>
77
{{range .Patterns}}
78
<tr>
79
<td><code>{{.Pattern}}</code></td>
80
<td>{{.Repo}}</td>
81
</tr>
82
{{end}}
83
</tbody>
84
</table>
85
{{end}}
86
87
{{if .HasAuth}}
88
<div class="note">Some modules are private and require authentication. Use a <code>Bearer</code> token or HTTP basic auth to access them.</div>
89
{{end}}
90
</div>
91
<footer>Powered by Curator</footer>
92
</body>
93
</html>
94
`))
95
96
func (s *Server) serveLanding(w http.ResponseWriter, r *http.Request) {
97
	data := map[string]any{
98
		"Host":         s.Cfg.Host,
99
		"SumdbEnabled": s.Cfg.Sumdb != nil && s.Cfg.Sumdb.Enabled,
100
		"HasAuth":      len(s.AuthTokens) > 0,
101
	}
102
103
	if s.ModuleLister != nil {
104
		modules, _ := s.ModuleLister.ListModules()
105
		patterns, _ := s.ModuleLister.ListPatterns()
106
107
		// Filter to public modules only.
108
		var publicModules []map[string]string
109
		for _, m := range modules {
110
			if !m.Private {
111
				publicModules = append(publicModules, map[string]string{
112
					"Name": m.Name,
113
					"Repo": m.Repo,
114
					"Web":  m.Web,
115
				})
116
			}
117
		}
118
		data["Modules"] = publicModules
119
120
		// Filter to public patterns only.
121
		var publicPatterns []map[string]string
122
		for _, p := range patterns {
123
			if !p.Private {
124
				publicPatterns = append(publicPatterns, map[string]string{
125
					"Pattern": p.Pattern,
126
					"Repo":    p.Repo,
127
				})
128
			}
129
		}
130
		data["Patterns"] = publicPatterns
131
	}
132
133
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
134
	if err := landingTmpl.Execute(w, data); err != nil {
135
		log.Printf("server: render landing: %v", err)
136
	}
137
}
138

Source Files