Files

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")
}