diff --git a/config.yml b/config.yml
index 4b68325..39e51a1 100644
--- a/config.yml
+++ b/config.yml
@@ -1,5 +1,7 @@
# Listen interface.
listen_addr: 0.0.0.0:8000
+# Base path of Docker Registry UI.
+base_path: /
# Registry URL with schema and port.
registry_url: https://docker-registry.local
diff --git a/example/.gitignore b/example/.gitignore
new file mode 100644
index 0000000..249cda9
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1 @@
+/data
\ No newline at end of file
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 0000000..b221eec
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,25 @@
+# Docker Registry UI Example
+
+Start the local Docker Registry and UI.
+
+```bash
+$ docker-compose up -d
+```
+
+As an example, push the docker-registry-ui image to the local Docker Registry.
+
+```bash
+$ docker tag quiq/docker-registry-ui localhost/quiq/docker-registry-ui
+$ docker push localhost/quiq/docker-registry-ui
+The push refers to repository [localhost:5000/quiq/docker-registry-ui]
+ab414a599bf8: Pushed
+a8da33adf86e: Pushed
+71a0e0a972a7: Pushed
+96dc74eb5456: Pushed
+ac362bf380d0: Pushed
+04a094fe844e: Pushed
+latest: digest: sha256:d88c1ca40986a358e59795992e87e364a0b3b97833aade5abcd79dda0a0477e8 size: 1571
+```
+
+Then you will find the pushed repository 'quiq/docker-registry-ui' in the following URL.
+http://localhost/ui/quiq/docker-registry-ui
diff --git a/example/config/httpd.conf b/example/config/httpd.conf
new file mode 100644
index 0000000..05d9e9c
--- /dev/null
+++ b/example/config/httpd.conf
@@ -0,0 +1,79 @@
+LoadModule mpm_event_module modules/mod_mpm_event.so
+LoadModule headers_module modules/mod_headers.so
+
+LoadModule authn_file_module modules/mod_authn_file.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
+LoadModule authz_user_module modules/mod_authz_user.so
+LoadModule authz_core_module modules/mod_authz_core.so
+LoadModule auth_basic_module modules/mod_auth_basic.so
+LoadModule access_compat_module modules/mod_access_compat.so
+
+LoadModule log_config_module modules/mod_log_config.so
+
+LoadModule proxy_module modules/mod_proxy.so
+LoadModule proxy_http_module modules/mod_proxy_http.so
+
+LoadModule unixd_module modules/mod_unixd.so
+
+
+ User daemon
+ Group daemon
+
+
+ServerAdmin you@example.com
+
+ErrorLog /proc/self/fd/2
+
+LogLevel warn
+
+
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+
+
+ CustomLog /proc/self/fd/1 common
+
+
+ServerRoot "/usr/local/apache2"
+
+Listen 80
+
+
+ AllowOverride none
+ Require all denied
+
+
+
+
+ ServerName myregistrydomain.com
+
+ Header always set "Docker-Distribution-Api-Version" "registry/2.0"
+ Header onsuccess set "Docker-Distribution-Api-Version" "registry/2.0"
+
+ ProxyRequests off
+ ProxyPreserveHost on
+
+ # no proxy for /error/ (Apache HTTPd errors messages)
+ ProxyPass /error/ !
+
+ ProxyPass /v2 http://registry:5000/v2
+ ProxyPassReverse /v2 http://registry:5000/v2
+
+
+ Order deny,allow
+ Allow from all
+
+
+ ProxyPass /ui/ http://registry-ui:8000/ui/
+ ProxyPassReverse /ui/ http://registry-ui:8000/ui/
+
+
+ Order deny,allow
+ Allow from all
+
+
+
\ No newline at end of file
diff --git a/example/config/registry-ui.yml b/example/config/registry-ui.yml
new file mode 100644
index 0000000..2cb042a
--- /dev/null
+++ b/example/config/registry-ui.yml
@@ -0,0 +1,46 @@
+# Listen interface.
+listen_addr: 0.0.0.0:8000
+# Base path of Docker Registry UI.
+base_path: /ui
+
+# Registry URL with schema and port.
+registry_url: http://registry:5000
+# Verify TLS certificate when using https.
+verify_tls: true
+
+# Docker registry credentials.
+# They need to have a full access to the registry.
+# If token authentication service is enabled, it will be auto-discovered and those credentials
+# will be used to obtain access tokens.
+# registry_username: user
+# registry_password: pass
+
+# Event listener token.
+# The same one should be configured on Docker registry as Authorization Bearer token.
+event_listener_token: token
+# Retention of records to keep.
+event_retention_days: 7
+
+# Event listener storage.
+event_database_driver: sqlite3
+event_database_location: data/registry_events.db
+# event_database_driver: mysql
+# event_database_location: user:password@tcp(localhost:3306)/docker_events
+
+# Cache refresh interval in minutes.
+# How long to cache repository list and tag counts.
+cache_refresh_interval: 10
+
+# If users can delete tags. If set to False, then only admins listed below.
+anyone_can_delete: false
+# Users allowed to delete tags.
+# This should be sent via X-WEBAUTH-USER header from your proxy.
+admins: []
+
+# Debug mode. Affects only templates.
+debug: true
+
+# CLI options.
+# How many days to keep tags but also keep the minimal count provided no matter how old.
+purge_tags_keep_days: 90
+purge_tags_keep_count: 2
diff --git a/example/docker-compose.yml b/example/docker-compose.yml
new file mode 100644
index 0000000..74c1987
--- /dev/null
+++ b/example/docker-compose.yml
@@ -0,0 +1,31 @@
+version: "2.3"
+
+services:
+ httpd:
+ image: httpd:2.4
+ ports:
+ - "80:80"
+ volumes:
+ - "./config/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro"
+
+ registry:
+ image: registry:2
+ ports:
+ - "5000"
+ volumes:
+ - "./data/registry:/var/lib/registry"
+ healthcheck:
+ test: ["CMD", "wget", "-s", "localhost:5000/v2/"]
+ interval: 5s
+ timeout: 10s
+
+ registry-ui:
+ image: quiq/docker-registry-ui:latest
+ ports:
+ - "8000"
+ volumes:
+ - "./data/registry-ui:/opt/data"
+ - "./config/registry-ui.yml:/opt/config.yml:ro"
+ depends_on:
+ registry:
+ condition: service_healthy
diff --git a/main.go b/main.go
index 06e04b5..bc346dc 100644
--- a/main.go
+++ b/main.go
@@ -21,6 +21,7 @@ import (
type configData struct {
ListenAddr string `yaml:"listen_addr"`
+ BasePath string `yaml:"base_path"`
RegistryURL string `yaml:"registry_url"`
VerifyTLS bool `yaml:"verify_tls"`
Username string `yaml:"registry_username"`
@@ -75,6 +76,15 @@ func main() {
if err != nil {
panic(err)
}
+ // Normalize base path.
+ if a.config.BasePath != "" {
+ if !strings.HasPrefix(a.config.BasePath, "/") {
+ a.config.BasePath = "/" + a.config.BasePath
+ }
+ if strings.HasSuffix(a.config.BasePath, "/") {
+ a.config.BasePath = a.config.BasePath[0 : len(a.config.BasePath)-1]
+ }
+ }
// Init registry API client.
a.client = registry.NewClient(a.config.RegistryURL, a.config.VerifyTLS, a.config.Username, a.config.Password)
@@ -99,6 +109,7 @@ func main() {
// 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
@@ -137,22 +148,22 @@ func main() {
e.Renderer = &template{View: view}
// Web routes.
- e.Static("/static", "static")
- e.GET("/", a.viewRepositories)
- e.GET("/:namespace", a.viewRepositories)
- e.GET("/:namespace/:repo", a.viewTags)
- e.GET("/:namespace/:repo/:tag", a.viewTagInfo)
- e.GET("/:namespace/:repo/:tag/delete", a.deleteTag)
- e.GET("/events", a.viewLog)
+ e.Static(a.config.BasePath+"/static", "static")
+ e.GET(a.config.BasePath+"/", a.viewRepositories)
+ e.GET(a.config.BasePath+"/:namespace", a.viewRepositories)
+ e.GET(a.config.BasePath+"/:namespace/:repo", a.viewTags)
+ e.GET(a.config.BasePath+"/:namespace/:repo/:tag", a.viewTagInfo)
+ e.GET(a.config.BasePath+"/:namespace/:repo/:tag/delete", a.deleteTag)
+ e.GET(a.config.BasePath+"/events", a.viewLog)
// Protected event listener.
- p := e.Group("/api")
+ p := e.Group(a.config.BasePath + "/api")
p.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
Validator: middleware.KeyAuthValidator(func(token string, c echo.Context) (bool, error) {
return token == a.config.EventListenerToken, nil
}),
}))
- p.POST("/events", a.receiveEvents)
+ p.POST(a.config.BasePath+"/events", a.receiveEvents)
e.Logger.Fatal(e.Start(a.config.ListenAddr))
}
@@ -181,7 +192,6 @@ func (a *apiClient) viewRepositories(c echo.Context) error {
}
repos, _ := a.client.Repositories(true)[namespace]
-
data := jet.VarMap{}
data.Set("namespace", namespace)
data.Set("namespaces", a.client.Namespaces())
diff --git a/templates/base.html b/templates/base.html
index 579fa3d..5a0abfd 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -15,7 +15,7 @@
Docker Registry UI
diff --git a/templates/event_log.html b/templates/event_log.html
index 29a485f..a62fc1a 100644
--- a/templates/event_log.html
+++ b/templates/event_log.html
@@ -14,7 +14,7 @@
{{block body()}}
- - Home
+ - Home
- Event Log
diff --git a/templates/repositories.html b/templates/repositories.html
index c4c9150..dfeb0bd 100644
--- a/templates/repositories.html
+++ b/templates/repositories.html
@@ -8,12 +8,14 @@
"stateSave": true
});
$('#namespace').on('change', function (e) {
- window.location = '/' + this.value;
+ window.location = '{{ base_path }}/' + this.value;
});
- if (window.location.pathname == '/') {
+ namespace = window.location.pathname;
+ namespace = namespace.replace("{{ base_path }}", "");
+ if (namespace == '/') {
namespace = 'library';
} else {
- namespace = window.location.pathname.split('/')[1]
+ namespace = namespace.split('/')[1]
}
$('#namespace').val(namespace);
});
@@ -35,7 +37,7 @@
- - Home
+ - Home
@@ -48,7 +50,7 @@
{{range repo := repos}}
- {{ repo }} |
+ {{ repo }} |
{{ tagCounts[namespace+"/"+repo] }} |
{{end}}
diff --git a/templates/tag_info.html b/templates/tag_info.html
index e522383..4310629 100644
--- a/templates/tag_info.html
+++ b/templates/tag_info.html
@@ -4,11 +4,11 @@
{{block body()}}
- - Home
+ - Home
{{if namespace != "library"}}
- - {{ namespace }}
+ - {{ namespace }}
{{end}}
- - {{ repo |url_decoded_path }}
+ - {{ repo }}
- {{ tag }}
diff --git a/templates/tags.html b/templates/tags.html
index fb24c70..c93f522 100644
--- a/templates/tags.html
+++ b/templates/tags.html
@@ -1,7 +1,7 @@
{{extends "base.html"}}
{{block head()}}
-
+