mirror of
https://github.com/Quiq/docker-registry-ui
synced 2026-04-22 15:47:08 +08:00
195 lines
6.1 KiB
Go
195 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/CloudyKit/jet/v6"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/quiq/registry-ui/registry"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
const usernameHTTPHeader = "X-WEBAUTH-USER"
|
|
|
|
func (a *apiClient) setUserPermissions(c echo.Context) jet.VarMap {
|
|
user := c.Request().Header.Get(usernameHTTPHeader)
|
|
|
|
data := jet.VarMap{}
|
|
data.Set("user", user)
|
|
admins := viper.GetStringSlice("access_control.admins")
|
|
data.Set("eventsAllowed", viper.GetBool("access_control.anyone_can_view_events") || registry.ItemInSlice(user, admins))
|
|
data.Set("deleteAllowed", viper.GetBool("access_control.anyone_can_delete_tags") || registry.ItemInSlice(user, admins))
|
|
return data
|
|
}
|
|
|
|
func (a *apiClient) viewCatalog(c echo.Context) error {
|
|
repoPath := strings.Trim(c.Param("repoPath"), "/")
|
|
// fmt.Println("repoPath:", repoPath)
|
|
|
|
data := a.setUserPermissions(c)
|
|
data.Set("repoPath", repoPath)
|
|
|
|
showTags := false
|
|
showImageInfo := false
|
|
allRepoPaths := a.client.GetRepos()
|
|
repos := []string{}
|
|
if repoPath == "" {
|
|
// Show all repos
|
|
for _, r := range allRepoPaths {
|
|
repos = append(repos, strings.Split(r, "/")[0])
|
|
}
|
|
} else if strings.Contains(repoPath, ":") {
|
|
// Show image info
|
|
showImageInfo = true
|
|
} else {
|
|
for _, r := range allRepoPaths {
|
|
if r == repoPath {
|
|
// Show tags
|
|
showTags = true
|
|
}
|
|
if strings.HasPrefix(r, repoPath+"/") {
|
|
// Show sub-repos
|
|
r = strings.TrimPrefix(r, repoPath+"/")
|
|
repos = append(repos, strings.Split(r, "/")[0])
|
|
}
|
|
}
|
|
}
|
|
|
|
if showImageInfo {
|
|
// Show image info
|
|
imageInfo, err := a.client.GetImageInfo(repoPath)
|
|
if err != nil {
|
|
basePath := viper.GetString("uri_base_path")
|
|
return c.Redirect(http.StatusSeeOther, basePath)
|
|
}
|
|
data.Set("ii", imageInfo)
|
|
return c.Render(http.StatusOK, "image_info.html", data)
|
|
} else {
|
|
// Show repos, tags or both.
|
|
repos = registry.UniqueSortedSlice(repos)
|
|
tags := []string{}
|
|
areTagsReady := false
|
|
tagsRefreshInfo := ""
|
|
if showTags {
|
|
tags, areTagsReady = a.client.ListTags(repoPath)
|
|
asyncInterval := viper.GetInt("performance.tags_async_refresh_interval")
|
|
maxCount := viper.GetInt("performance.tags_online_refresh_max_count")
|
|
if asyncInterval == 0 {
|
|
tagsRefreshInfo = "Tags for this repo are refreshed online when browsing."
|
|
} else if maxCount == 0 {
|
|
tagsRefreshInfo = fmt.Sprintf("Tags for this repo are refreshed in the background every %d min.", asyncInterval)
|
|
} else if len(tags) <= maxCount {
|
|
tagsRefreshInfo = fmt.Sprintf("Tags for this repo are refreshed in the background every %d min and online when browsing.", asyncInterval)
|
|
} else {
|
|
tagsRefreshInfo = fmt.Sprintf("Tags for this repo are refreshed in the background every %d min. Online refresh is disabled (tag count exceeds %d).", asyncInterval, maxCount)
|
|
}
|
|
}
|
|
data.Set("repos", repos)
|
|
data.Set("showTags", showTags)
|
|
data.Set("isCatalogReady", a.client.IsCatalogReady())
|
|
data.Set("areTagsReady", areTagsReady)
|
|
data.Set("tagsRefreshInfo", tagsRefreshInfo)
|
|
data.Set("tagCounts", a.client.SubRepoTagCounts(repoPath, repos))
|
|
data.Set("tags", tags)
|
|
if repoPath != "" && (len(repos) > 0 || len(tags) > 0) {
|
|
// Do not show events in the root of catalog.
|
|
data.Set("events", a.eventListener.GetEvents(repoPath))
|
|
}
|
|
return c.Render(http.StatusOK, "catalog.html", data)
|
|
}
|
|
}
|
|
|
|
func (a *apiClient) deleteTag(c echo.Context) error {
|
|
repoPath := c.QueryParam("repoPath")
|
|
tag := c.QueryParam("tag")
|
|
|
|
data := a.setUserPermissions(c)
|
|
if data["deleteAllowed"].Bool() {
|
|
a.client.DeleteTag(repoPath, tag)
|
|
}
|
|
basePath := viper.GetString("uri_base_path")
|
|
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s%s", basePath, repoPath))
|
|
}
|
|
|
|
// viewLog view events from sqlite.
|
|
func (a *apiClient) viewEventLog(c echo.Context) error {
|
|
data := a.setUserPermissions(c)
|
|
data.Set("events", a.eventListener.GetEvents(""))
|
|
return c.Render(http.StatusOK, "event_log.html", data)
|
|
}
|
|
|
|
// viewStatistics view registry statistics.
|
|
func (a *apiClient) viewStatistics(c echo.Context) error {
|
|
data := a.setUserPermissions(c)
|
|
data.Set("isCatalogReady", a.client.IsCatalogReady())
|
|
data.Set("repoCount", len(a.client.GetRepos()))
|
|
data.Set("tagCount", a.client.GetTotalTagCount())
|
|
data.Set("eventCount", a.eventListener.GetEventCount())
|
|
data.Set("topRepos", a.client.GetTopReposByTagCount(10))
|
|
catalogJobInfo, tagsJobInfo := a.client.GetJobInfo()
|
|
data.Set("catalogJobInfo", catalogJobInfo)
|
|
data.Set("tagsJobInfo", tagsJobInfo)
|
|
return c.Render(http.StatusOK, "statistics.html", data)
|
|
}
|
|
|
|
type configSection struct {
|
|
Name string
|
|
Options [][2]string // key, value pairs
|
|
}
|
|
|
|
// viewOptions view configuration options.
|
|
func (a *apiClient) viewOptions(c echo.Context) error {
|
|
data := a.setUserPermissions(c)
|
|
|
|
sensitiveKeys := map[string]bool{
|
|
"registry.password": true,
|
|
"registry.password_file": true,
|
|
"event_listener.bearer_token": true,
|
|
"event_listener.database_location": true,
|
|
}
|
|
|
|
settings := viper.AllSettings()
|
|
sectionMap := map[string][][2]string{}
|
|
for sectionName, v := range settings {
|
|
if nested, ok := v.(map[string]interface{}); ok {
|
|
keys := make([]string, 0, len(nested))
|
|
for k := range nested {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
val := fmt.Sprintf("%v", nested[k])
|
|
if sensitiveKeys[sectionName+"."+k] {
|
|
val = "***"
|
|
}
|
|
sectionMap[sectionName] = append(sectionMap[sectionName], [2]string{k, val})
|
|
}
|
|
} else {
|
|
sectionMap["general"] = append(sectionMap["general"], [2]string{sectionName, fmt.Sprintf("%v", v)})
|
|
}
|
|
}
|
|
|
|
sectionNames := make([]string, 0, len(sectionMap))
|
|
for name := range sectionMap {
|
|
sectionNames = append(sectionNames, name)
|
|
}
|
|
sort.Strings(sectionNames)
|
|
|
|
sections := make([]configSection, 0, len(sectionNames))
|
|
for _, name := range sectionNames {
|
|
sections = append(sections, configSection{Name: name, Options: sectionMap[name]})
|
|
}
|
|
|
|
data.Set("sections", sections)
|
|
return c.Render(http.StatusOK, "options.html", data)
|
|
}
|
|
|
|
// receiveEvents receive events.
|
|
func (a *apiClient) receiveEvents(c echo.Context) error {
|
|
a.eventListener.ProcessEvents(c.Request())
|
|
return c.String(http.StatusOK, "OK")
|
|
}
|