diff --git a/CHANGELOG.md b/CHANGELOG.md index b71cf13..cb3b2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ ## Changelog -### Unreleased +### 0.7 * When using MySQL for event storage, do not leak connections. * Last events were not shown when viewing a repo of non-default namespace. +* Support repos with slash in the name. +* Enable Sonatype Nexus compatibility. +* Add `base_path` option to the config to run UI from non-root. +* Add built-in cron feature for purging tags task. ### 0.6 diff --git a/main.go b/main.go index b5cbe3f..f0e6969 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - "io" "io/ioutil" "net/http" "net/url" @@ -107,44 +106,8 @@ func main() { a.eventListener = events.NewEventListener(a.config.EventDatabaseDriver, a.config.EventDatabaseLocation, a.config.EventRetentionDays) // Template engine init. - view := jet.NewHTMLSet("templates") - view.SetDevelopmentMode(a.config.Debug) - view.AddGlobal("base_path", a.config.BasePath) - view.AddGlobal("registryHost", u.Host) - view.AddGlobal("pretty_size", func(size interface{}) string { - var value float64 - switch i := size.(type) { - case gjson.Result: - value = float64(i.Int()) - case int64: - value = float64(i) - } - return registry.PrettySize(value) - }) - view.AddGlobal("pretty_time", func(datetime interface{}) string { - d := strings.Replace(datetime.(string), "T", " ", 1) - d = strings.Replace(d, "Z", "", 1) - return strings.Split(d, ".")[0] - }) - view.AddGlobal("parse_map", func(m interface{}) string { - var res string - for _, k := range registry.SortedMapKeys(m) { - res = res + fmt.Sprintf(`%s%v`, k, m.(map[string]interface{})[k]) - } - return res - }) - view.AddGlobal("url_encoded_path", func(m interface{}) string { - return url.PathEscape(m.(string)) - }) - view.AddGlobal("url_decoded_path", func(m interface{}) string { - res, err := url.PathUnescape(m.(string)) - if err != nil { - return m.(string) - } - return res - }) e := echo.New() - e.Renderer = &template{View: view} + e.Renderer = setupRenderer(a.config.Debug, u.Host, a.config.BasePath) // Web routes. e.Static(a.config.BasePath+"/static", "static") @@ -162,28 +125,11 @@ func main() { return token == a.config.EventListenerToken, nil }), })) - p.POST(a.config.BasePath+"/events", a.receiveEvents) + p.POST("/events", a.receiveEvents) e.Logger.Fatal(e.Start(a.config.ListenAddr)) } -// Render render template. -func (r *template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { - t, err := r.View.GetTemplate(name) - if err != nil { - panic(fmt.Errorf("Fatal error template file: %s", err)) - } - vars, ok := data.(jet.VarMap) - if !ok { - vars = jet.VarMap{} - } - err = t.Execute(w, vars, nil) - if err != nil { - panic(fmt.Errorf("Error rendering template %s: %s", name, err)) - } - return nil -} - func (a *apiClient) viewRepositories(c echo.Context) error { namespace := c.Param("namespace") if namespace == "" { @@ -233,7 +179,7 @@ func (a *apiClient) viewTagInfo(c echo.Context) error { sha256, infoV1, infoV2 := a.client.TagInfo(repoPath, tag, false) if infoV1 == "" || infoV2 == "" { - return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/%s/%s", namespace, repo)) + return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s/%s/%s", a.config.BasePath, namespace, repo)) } var imageSize int64 @@ -293,7 +239,7 @@ func (a *apiClient) deleteTag(c echo.Context) error { a.client.DeleteTag(repoPath, tag) } - return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/%s/%s", namespace, repo)) + return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s/%s/%s", a.config.BasePath, namespace, repo)) } // checkDeletePermission check if tag deletion is allowed whether by anyone or permitted users. diff --git a/registry/client.go b/registry/client.go index c94ed59..b162323 100644 --- a/registry/client.go +++ b/registry/client.go @@ -69,7 +69,7 @@ func NewClient(url string, verifyTLS bool, username, password string) *Client { c.logger.Warn("No token auth service discovered from ", c.url) return nil } - } else if strings.HasPrefix(strings.ToLower(authHeader), strings.ToLower("Basic")) { + } else if strings.HasPrefix(strings.ToLower(authHeader), "basic") { c.request = c.request.SetBasicAuth(c.username, c.password) c.logger.Info("It was discovered the registry is configured with HTTP basic auth.") } diff --git a/template.go b/template.go new file mode 100644 index 0000000..becd351 --- /dev/null +++ b/template.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "io" + "net/url" + "strings" + + "github.com/CloudyKit/jet" + "github.com/labstack/echo" + "github.com/quiq/docker-registry-ui/registry" + "github.com/tidwall/gjson" +) + +// Template Jet template. +type Template struct { + View *jet.Set +} + +// Render render template. +func (r *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + t, err := r.View.GetTemplate(name) + if err != nil { + panic(fmt.Errorf("Fatal error template file: %s", err)) + } + vars, ok := data.(jet.VarMap) + if !ok { + vars = jet.VarMap{} + } + err = t.Execute(w, vars, nil) + if err != nil { + panic(fmt.Errorf("Error rendering template %s: %s", name, err)) + } + return nil +} + +// setupRenderer template engine init. +func setupRenderer(debug bool, registryHost, basePath string) *Template { + view := jet.NewHTMLSet("templates") + view.SetDevelopmentMode(debug) + + view.AddGlobal("basePath", basePath) + view.AddGlobal("registryHost", registryHost) + view.AddGlobal("pretty_size", func(size interface{}) string { + var value float64 + switch i := size.(type) { + case gjson.Result: + value = float64(i.Int()) + case int64: + value = float64(i) + } + return registry.PrettySize(value) + }) + view.AddGlobal("pretty_time", func(datetime interface{}) string { + d := strings.Replace(datetime.(string), "T", " ", 1) + d = strings.Replace(d, "Z", "", 1) + return strings.Split(d, ".")[0] + }) + view.AddGlobal("parse_map", func(m interface{}) string { + var res string + for _, k := range registry.SortedMapKeys(m) { + res = res + fmt.Sprintf(`%s%v`, k, m.(map[string]interface{})[k]) + } + return res + }) + view.AddGlobal("url_decode", func(m interface{}) string { + res, err := url.PathUnescape(m.(string)) + if err != nil { + return m.(string) + } + return res + }) + + return &Template{View: view} +} diff --git a/templates/base.html b/templates/base.html index 5a0abfd..8ef0a93 100644 --- a/templates/base.html +++ b/templates/base.html @@ -15,7 +15,7 @@

Docker Registry UI

- Event Log + Event Log
diff --git a/templates/event_log.html b/templates/event_log.html index a62fc1a..9c4c400 100644 --- a/templates/event_log.html +++ b/templates/event_log.html @@ -14,7 +14,7 @@ {{block body()}} diff --git a/templates/repositories.html b/templates/repositories.html index dfeb0bd..687c403 100644 --- a/templates/repositories.html +++ b/templates/repositories.html @@ -8,10 +8,10 @@ "stateSave": true }); $('#namespace').on('change', function (e) { - window.location = '{{ base_path }}/' + this.value; + window.location = '{{ basePath }}/' + this.value; }); namespace = window.location.pathname; - namespace = namespace.replace("{{ base_path }}", ""); + namespace = namespace.replace("{{ basePath }}", ""); if (namespace == '/') { namespace = 'library'; } else { @@ -37,7 +37,7 @@ @@ -50,7 +50,7 @@ {{range repo := repos}} - + {{end}} diff --git a/templates/tag_info.html b/templates/tag_info.html index 4310629..6095394 100644 --- a/templates/tag_info.html +++ b/templates/tag_info.html @@ -4,11 +4,11 @@ {{block body()}}
{{ repo }}{{ repo }} {{ tagCounts[namespace+"/"+repo] }}
diff --git a/templates/tags.html b/templates/tags.html index c93f522..84429a6 100644 --- a/templates/tags.html +++ b/templates/tags.html @@ -1,7 +1,7 @@ {{extends "base.html"}} {{block head()}} - +