mirror of
https://github.com/Quiq/docker-registry-ui.git
synced 2025-07-18 16:21:24 +00:00
A bit refactoring.
This commit is contained in:
parent
e163b5af27
commit
d1ce70490c
@ -1,9 +1,13 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
### Unreleased
|
### 0.7
|
||||||
|
|
||||||
* When using MySQL for event storage, do not leak connections.
|
* When using MySQL for event storage, do not leak connections.
|
||||||
* Last events were not shown when viewing a repo of non-default namespace.
|
* 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
|
### 0.6
|
||||||
|
|
||||||
|
62
main.go
62
main.go
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -107,44 +106,8 @@ func main() {
|
|||||||
a.eventListener = events.NewEventListener(a.config.EventDatabaseDriver, a.config.EventDatabaseLocation, a.config.EventRetentionDays)
|
a.eventListener = events.NewEventListener(a.config.EventDatabaseDriver, a.config.EventDatabaseLocation, a.config.EventRetentionDays)
|
||||||
|
|
||||||
// Template engine init.
|
// 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(`<tr><td style="padding: 0 10px; width: 20%%">%s</td><td style="padding: 0 10px">%v</td></tr>`, 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 := echo.New()
|
||||||
e.Renderer = &template{View: view}
|
e.Renderer = setupRenderer(a.config.Debug, u.Host, a.config.BasePath)
|
||||||
|
|
||||||
// Web routes.
|
// Web routes.
|
||||||
e.Static(a.config.BasePath+"/static", "static")
|
e.Static(a.config.BasePath+"/static", "static")
|
||||||
@ -162,28 +125,11 @@ func main() {
|
|||||||
return token == a.config.EventListenerToken, nil
|
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))
|
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 {
|
func (a *apiClient) viewRepositories(c echo.Context) error {
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
@ -233,7 +179,7 @@ func (a *apiClient) viewTagInfo(c echo.Context) error {
|
|||||||
|
|
||||||
sha256, infoV1, infoV2 := a.client.TagInfo(repoPath, tag, false)
|
sha256, infoV1, infoV2 := a.client.TagInfo(repoPath, tag, false)
|
||||||
if infoV1 == "" || infoV2 == "" {
|
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
|
var imageSize int64
|
||||||
@ -293,7 +239,7 @@ func (a *apiClient) deleteTag(c echo.Context) error {
|
|||||||
a.client.DeleteTag(repoPath, tag)
|
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.
|
// checkDeletePermission check if tag deletion is allowed whether by anyone or permitted users.
|
||||||
|
@ -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)
|
c.logger.Warn("No token auth service discovered from ", c.url)
|
||||||
return nil
|
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.request = c.request.SetBasicAuth(c.username, c.password)
|
||||||
c.logger.Info("It was discovered the registry is configured with HTTP basic auth.")
|
c.logger.Info("It was discovered the registry is configured with HTTP basic auth.")
|
||||||
}
|
}
|
||||||
|
75
template.go
Normal file
75
template.go
Normal file
@ -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(`<tr><td style="padding: 0 10px; width: 20%%">%s</td><td style="padding: 0 10px">%v</td></tr>`, 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}
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
<h2>Docker Registry UI</h2>
|
<h2>Docker Registry UI</h2>
|
||||||
</div>
|
</div>
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<a href="{{ base_path }}/events">Event Log</a>
|
<a href="{{ basePath }}/events">Event Log</a>
|
||||||
</div>
|
</div>
|
||||||
<div style="clear: both"></div>
|
<div style="clear: both"></div>
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
{{block body()}}
|
{{block body()}}
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{{ base_path }}/">Home</a></li>
|
<li><a href="{{ basePath }}/">Home</a></li>
|
||||||
<li class="active">Event Log</li>
|
<li class="active">Event Log</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
"stateSave": true
|
"stateSave": true
|
||||||
});
|
});
|
||||||
$('#namespace').on('change', function (e) {
|
$('#namespace').on('change', function (e) {
|
||||||
window.location = '{{ base_path }}/' + this.value;
|
window.location = '{{ basePath }}/' + this.value;
|
||||||
});
|
});
|
||||||
namespace = window.location.pathname;
|
namespace = window.location.pathname;
|
||||||
namespace = namespace.replace("{{ base_path }}", "");
|
namespace = namespace.replace("{{ basePath }}", "");
|
||||||
if (namespace == '/') {
|
if (namespace == '/') {
|
||||||
namespace = 'library';
|
namespace = 'library';
|
||||||
} else {
|
} else {
|
||||||
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{{ base_path }}/">Home</a></li>
|
<li><a href="{{ basePath }}/">Home</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<table id="datatable" class="table table-striped table-bordered">
|
<table id="datatable" class="table table-striped table-bordered">
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{range repo := repos}}
|
{{range repo := repos}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ base_path }}/{{ namespace }}/{{ repo|url_encoded_path }}">{{ repo }}</a></td>
|
<td><a href="{{ basePath }}/{{ namespace }}/{{ repo|url }}">{{ repo }}</a></td>
|
||||||
<td>{{ tagCounts[namespace+"/"+repo] }}</td>
|
<td>{{ tagCounts[namespace+"/"+repo] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
{{block body()}}
|
{{block body()}}
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{{ base_path }}/">Home</a></li>
|
<li><a href="{{ basePath }}/">Home</a></li>
|
||||||
{{if namespace != "library"}}
|
{{if namespace != "library"}}
|
||||||
<li><a href="{{ base_path }}/{{ namespace }}">{{ namespace }}</a></li>
|
<li><a href="{{ basePath }}/{{ namespace }}">{{ namespace }}</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
<li><a href="{{ base_path }}/{{ namespace }}/{{ repo|url_decoded_path }}">{{ repo }}</a></li>
|
<li><a href="{{ basePath }}/{{ namespace }}/{{ repo }}">{{ repo|url_decode }}</a></li>
|
||||||
<li class="active">{{ tag }}</li>
|
<li class="active">{{ tag }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{{extends "base.html"}}
|
{{extends "base.html"}}
|
||||||
|
|
||||||
{{block head()}}
|
{{block head()}}
|
||||||
<script type="text/javascript" src="{{ base_path }}/static/bootstrap-confirmation.min.js"></script>
|
<script type="text/javascript" src="{{ basePath }}/static/bootstrap-confirmation.min.js"></script>
|
||||||
<script type="text/javascript" src="https://cdn.datatables.net/plug-ins/1.10.16/sorting/natural.js"></script>
|
<script type="text/javascript" src="https://cdn.datatables.net/plug-ins/1.10.16/sorting/natural.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@ -29,11 +29,11 @@
|
|||||||
|
|
||||||
{{block body()}}
|
{{block body()}}
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{{ base_path }}/">Home</a></li>
|
<li><a href="{{ basePath }}/">Home</a></li>
|
||||||
{{if namespace != "library"}}
|
{{if namespace != "library"}}
|
||||||
<li><a href="{{ base_path }}/{{ namespace }}">{{ namespace }}</a></li>
|
<li><a href="{{ basePath }}/{{ namespace }}">{{ namespace }}</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
<li class="active">{{ repo |url_decoded_path }}</li>
|
<li class="active">{{ repo|url_decode }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<table id="datatable" class="table table-striped table-bordered">
|
<table id="datatable" class="table table-striped table-bordered">
|
||||||
@ -46,9 +46,9 @@
|
|||||||
{{range tag := tags}}
|
{{range tag := tags}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ base_path }}/{{ namespace }}/{{ repo }}/{{ tag }}">{{ tag }}</a>
|
<a href="{{ basePath }}/{{ namespace }}/{{ repo }}/{{ tag }}">{{ tag }}</a>
|
||||||
{{if deleteAllowed}}
|
{{if deleteAllowed}}
|
||||||
<a href="{{ base_path }}/{{ namespace }}/{{ repo }}/{{ tag }}/delete" data-toggle="confirmation" class="btn btn-danger btn-xs pull-right" role="button">Delete</a>
|
<a href="{{ basePath }}/{{ namespace }}/{{ repo }}/{{ tag }}/delete" data-toggle="confirmation" class="btn btn-danger btn-xs pull-right" role="button">Delete</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
Loading…
Reference in New Issue
Block a user