mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-24 17:28:31 +02:00
feat(homepage): enhance homepage functionality with new item click tracking, sort methods and category management
- Added ItemClick endpoint to increment item click counts. - Refactored Categories function to dynamically generate categories based on available items. - Introduced sorting methods for homepage items and categories. - Updated item configuration to include visibility, favorite status, and sort orders. - Improved handling of item URLs and added support for websocket connections in item retrieval.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,5 +19,24 @@ import (
|
|||||||
// @Failure 403 {object} apitypes.ErrorResponse
|
// @Failure 403 {object} apitypes.ErrorResponse
|
||||||
// @Router /homepage/categories [get]
|
// @Router /homepage/categories [get]
|
||||||
func Categories(c *gin.Context) {
|
func Categories(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, routes.HomepageCategories())
|
c.JSON(http.StatusOK, HomepageCategories())
|
||||||
|
}
|
||||||
|
|
||||||
|
func HomepageCategories() []string {
|
||||||
|
check := make(map[string]struct{})
|
||||||
|
categories := make([]string, 0)
|
||||||
|
categories = append(categories, homepage.CategoryAll)
|
||||||
|
categories = append(categories, homepage.CategoryFavorites)
|
||||||
|
for _, r := range routes.HTTP.Iter {
|
||||||
|
item := r.HomepageItem()
|
||||||
|
if item.Category == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := check[item.Category]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
check[item.Category] = struct{}{}
|
||||||
|
categories = append(categories, item.Category)
|
||||||
|
}
|
||||||
|
return categories
|
||||||
}
|
}
|
||||||
|
|||||||
36
internal/api/v1/homepage/item_click.go
Normal file
36
internal/api/v1/homepage/item_click.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package homepageapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
apitypes "github.com/yusing/go-proxy/internal/api/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HomepageOverrideItemClickParams struct {
|
||||||
|
Which string `form:"which" binding:"required"`
|
||||||
|
} // @name HomepageOverrideItemClickParams
|
||||||
|
|
||||||
|
// @x-id "item-click"
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @Summary Increment item click
|
||||||
|
// @Description Increment item click.
|
||||||
|
// @Tags homepage
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request query HomepageOverrideItemClickParams true "Increment item click"
|
||||||
|
// @Success 200 {object} apitypes.SuccessResponse
|
||||||
|
// @Failure 400 {object} apitypes.ErrorResponse
|
||||||
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
|
// @Router /homepage/item_click [post]
|
||||||
|
func ItemClick(c *gin.Context) {
|
||||||
|
var params HomepageOverrideItemClickParams
|
||||||
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overrides := homepage.GetOverrideConfig()
|
||||||
|
overrides.IncrementItemClicks(params.Which)
|
||||||
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
|
}
|
||||||
@@ -1,27 +1,38 @@
|
|||||||
package homepageapi
|
package homepageapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
apitypes "github.com/yusing/go-proxy/internal/api/types"
|
apitypes "github.com/yusing/go-proxy/internal/api/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
|
||||||
|
"github.com/yusing/go-proxy/internal/net/gphttp/websocket"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HomepageItemsRequest struct {
|
type HomepageItemsRequest struct {
|
||||||
Category string `form:"category" validate:"omitempty"`
|
SearchQuery string `form:"search"` // Search query
|
||||||
Provider string `form:"provider" validate:"omitempty"`
|
Category string `form:"category"` // Category filter
|
||||||
|
Provider string `form:"provider"` // Provider filter
|
||||||
|
// Sort method
|
||||||
|
SortMethod homepage.SortMethod `form:"sort_method" default:"alphabetical" binding:"omitempty,oneof=clicks alphabetical custom"`
|
||||||
} // @name HomepageItemsRequest
|
} // @name HomepageItemsRequest
|
||||||
|
|
||||||
// @x-id "items"
|
// @x-id "items"
|
||||||
// @BasePath /api/v1
|
// @BasePath /api/v1
|
||||||
// @Summary Homepage items
|
// @Summary Homepage items
|
||||||
// @Description Homepage items
|
// @Description Homepage items
|
||||||
// @Tags homepage
|
// @Tags homepage,websocket
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param category query string false "Category filter"
|
// @Param query query HomepageItemsRequest false "Query parameters"
|
||||||
// @Param provider query string false "Provider filter"
|
|
||||||
// @Success 200 {object} homepage.Homepage
|
// @Success 200 {object} homepage.Homepage
|
||||||
// @Failure 400 {object} apitypes.ErrorResponse
|
// @Failure 400 {object} apitypes.ErrorResponse
|
||||||
// @Failure 403 {object} apitypes.ErrorResponse
|
// @Failure 403 {object} apitypes.ErrorResponse
|
||||||
@@ -42,5 +53,81 @@ func Items(c *gin.Context) {
|
|||||||
hostname = host
|
hostname = host
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, routes.HomepageItems(proto, hostname, request.Category, request.Provider))
|
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||||
|
websocket.PeriodicWrite(c, 2*time.Second, func() (any, error) {
|
||||||
|
return HomepageItems(proto, hostname, &request), nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, HomepageItems(proto, hostname, &request))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HomepageItems(proto, hostname string, request *HomepageItemsRequest) homepage.Homepage {
|
||||||
|
switch proto {
|
||||||
|
case "http", "https":
|
||||||
|
default:
|
||||||
|
proto = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
hp := homepage.NewHomepageMap(routes.HTTP.Size())
|
||||||
|
|
||||||
|
if strings.Count(hostname, ".") > 1 {
|
||||||
|
_, hostname, _ = strings.Cut(hostname, ".") // remove the subdomain
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range routes.HTTP.Iter {
|
||||||
|
if request.Provider != "" && r.ProviderName() != request.Provider {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
item := r.HomepageItem()
|
||||||
|
if request.Category != "" && item.Category != request.Category {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if request.SearchQuery != "" && !fuzzy.MatchFold(request.SearchQuery, item.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear url if invalid
|
||||||
|
_, err := url.Parse(item.URL)
|
||||||
|
if err != nil {
|
||||||
|
item.URL = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// append hostname if provided and only if alias is not FQDN
|
||||||
|
if hostname != "" && item.URL == "" {
|
||||||
|
isFQDNAlias := strings.Contains(item.Alias, ".")
|
||||||
|
if !isFQDNAlias {
|
||||||
|
item.URL = fmt.Sprintf("%s://%s.%s", proto, item.Alias, hostname)
|
||||||
|
} else {
|
||||||
|
item.URL = fmt.Sprintf("%s://%s", proto, item.Alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend protocol if not exists
|
||||||
|
if !strings.HasPrefix(item.URL, "http://") && !strings.HasPrefix(item.URL, "https://") {
|
||||||
|
item.URL = fmt.Sprintf("%s://%s", proto, item.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
hp.Add(&item)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := hp.Values()
|
||||||
|
// sort items in each category
|
||||||
|
for _, category := range ret {
|
||||||
|
category.Sort(request.SortMethod)
|
||||||
|
}
|
||||||
|
// sort categories
|
||||||
|
overrides := homepage.GetOverrideConfig()
|
||||||
|
slices.SortStableFunc(ret, func(a, b *homepage.Category) int {
|
||||||
|
// if category is "Hidden", move it to the end of the list
|
||||||
|
if a.Name == homepage.CategoryHidden {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if b.Name == homepage.CategoryHidden {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// sort categories by order in config
|
||||||
|
return overrides.CategoryOrder[a.Name] - overrides.CategoryOrder[b.Name]
|
||||||
|
})
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package homepageapi
|
package homepageapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -15,16 +14,22 @@ type (
|
|||||||
Value homepage.ItemConfig `json:"value"`
|
Value homepage.ItemConfig `json:"value"`
|
||||||
} // @name HomepageOverrideItemParams
|
} // @name HomepageOverrideItemParams
|
||||||
HomepageOverrideItemsBatchParams struct {
|
HomepageOverrideItemsBatchParams struct {
|
||||||
Value map[string]*homepage.ItemConfig `json:"value"`
|
Value map[string]homepage.ItemConfig `json:"value"`
|
||||||
} // @name HomepageOverrideItemsBatchParams
|
} // @name HomepageOverrideItemsBatchParams
|
||||||
|
|
||||||
HomepageOverrideCategoryOrderParams struct {
|
HomepageOverrideCategoryOrderParams struct {
|
||||||
Which string `json:"which"`
|
Which string `json:"which"`
|
||||||
Value int `json:"value"`
|
Value int `json:"value"`
|
||||||
} // @name HomepageOverrideCategoryOrderParams
|
} // @name HomepageOverrideCategoryOrderParams
|
||||||
|
HomepageOverrideItemSortOrderParams HomepageOverrideCategoryOrderParams // @name HomepageOverrideItemSortOrderParams
|
||||||
|
HomepageOverrideItemAllSortOrderParams HomepageOverrideCategoryOrderParams // @name HomepageOverrideItemAllSortOrderParams
|
||||||
|
HomepageOverrideItemFavSortOrderParams HomepageOverrideCategoryOrderParams // @name HomepageOverrideItemFavSortOrderParams
|
||||||
|
|
||||||
HomepageOverrideItemVisibleParams struct {
|
HomepageOverrideItemVisibleParams struct {
|
||||||
Which []string `json:"which"`
|
Which []string `json:"which"`
|
||||||
Value bool `json:"value"`
|
Value bool `json:"value"`
|
||||||
} // @name HomepageOverrideItemVisibleParams
|
} // @name HomepageOverrideItemVisibleParams
|
||||||
|
HomepageOverrideItemFavoriteParams HomepageOverrideItemVisibleParams // @name HomepageOverrideItemFavoriteParams
|
||||||
)
|
)
|
||||||
|
|
||||||
// @x-id "set-item"
|
// @x-id "set-item"
|
||||||
@@ -46,7 +51,7 @@ func SetItem(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
overrides := homepage.GetOverrideConfig()
|
overrides := homepage.GetOverrideConfig()
|
||||||
overrides.OverrideItem(params.Which, ¶ms.Value)
|
overrides.OverrideItem(params.Which, params.Value)
|
||||||
c.JSON(http.StatusOK, apitypes.Success("success"))
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,15 +70,8 @@ func SetItem(c *gin.Context) {
|
|||||||
func SetItemsBatch(c *gin.Context) {
|
func SetItemsBatch(c *gin.Context) {
|
||||||
var params HomepageOverrideItemsBatchParams
|
var params HomepageOverrideItemsBatchParams
|
||||||
if err := c.ShouldBindJSON(¶ms); err != nil {
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
data, derr := c.GetRawData()
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
if derr != nil {
|
return
|
||||||
c.Error(apitypes.InternalServerError(derr, "failed to get raw data"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if uerr := json.Unmarshal(data, ¶ms); uerr != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", uerr))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
overrides := homepage.GetOverrideConfig()
|
overrides := homepage.GetOverrideConfig()
|
||||||
overrides.OverrideItems(params.Value)
|
overrides.OverrideItems(params.Value)
|
||||||
@@ -95,22 +93,103 @@ func SetItemsBatch(c *gin.Context) {
|
|||||||
func SetItemVisible(c *gin.Context) {
|
func SetItemVisible(c *gin.Context) {
|
||||||
var params HomepageOverrideItemVisibleParams
|
var params HomepageOverrideItemVisibleParams
|
||||||
if err := c.ShouldBindJSON(¶ms); err != nil {
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
data, derr := c.GetRawData()
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
if derr != nil {
|
return
|
||||||
c.Error(apitypes.InternalServerError(derr, "failed to get raw data"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if uerr := json.Unmarshal(data, ¶ms); uerr != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", uerr))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
overrides := homepage.GetOverrideConfig()
|
overrides := homepage.GetOverrideConfig()
|
||||||
if params.Value {
|
overrides.SetItemsVisibility(params.Which, params.Value)
|
||||||
overrides.UnhideItems(params.Which)
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
} else {
|
}
|
||||||
overrides.HideItems(params.Which)
|
|
||||||
|
// @x-id "set-item-favorite"
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @Summary Set homepage item favorite
|
||||||
|
// @Description Set homepage item favorite.
|
||||||
|
// @Tags homepage
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body HomepageOverrideItemFavoriteParams true "Set item favorite"
|
||||||
|
// @Success 200 {object} apitypes.SuccessResponse
|
||||||
|
// @Failure 400 {object} apitypes.ErrorResponse
|
||||||
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
|
// @Router /homepage/set/item_favorite [post]
|
||||||
|
func SetItemFavorite(c *gin.Context) {
|
||||||
|
var params HomepageOverrideItemFavoriteParams
|
||||||
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
overrides := homepage.GetOverrideConfig()
|
||||||
|
overrides.SetItemsFavorite(params.Which, params.Value)
|
||||||
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @x-id "set-item-sort-order"
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @Summary Set homepage item sort order
|
||||||
|
// @Description Set homepage item sort order.
|
||||||
|
// @Tags homepage
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body HomepageOverrideItemSortOrderParams true "Set item sort order"
|
||||||
|
// @Success 200 {object} apitypes.SuccessResponse
|
||||||
|
// @Failure 400 {object} apitypes.ErrorResponse
|
||||||
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
|
// @Router /homepage/set/item_sort_order [post]
|
||||||
|
func SetItemSortOrder(c *gin.Context) {
|
||||||
|
var params HomepageOverrideItemSortOrderParams
|
||||||
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overrides := homepage.GetOverrideConfig()
|
||||||
|
overrides.SetSortOrder(params.Which, params.Value)
|
||||||
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @x-id "set-item-all-sort-order"
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @Summary Set homepage item all sort order
|
||||||
|
// @Description Set homepage item all sort order.
|
||||||
|
// @Tags homepage
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body HomepageOverrideItemAllSortOrderParams true "Set item all sort order"
|
||||||
|
// @Success 200 {object} apitypes.SuccessResponse
|
||||||
|
// @Failure 400 {object} apitypes.ErrorResponse
|
||||||
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
|
// @Router /homepage/set/item_all_sort_order [post]
|
||||||
|
func SetItemAllSortOrder(c *gin.Context) {
|
||||||
|
var params HomepageOverrideItemAllSortOrderParams
|
||||||
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overrides := homepage.GetOverrideConfig()
|
||||||
|
overrides.SetAllSortOrder(params.Which, params.Value)
|
||||||
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @x-id "set-item-fav-sort-order"
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @Summary Set homepage item fav sort order
|
||||||
|
// @Description Set homepage item fav sort order.
|
||||||
|
// @Tags homepage
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body HomepageOverrideItemFavSortOrderParams true "Set item fav sort order"
|
||||||
|
// @Success 200 {object} apitypes.SuccessResponse
|
||||||
|
// @Failure 400 {object} apitypes.ErrorResponse
|
||||||
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
|
// @Router /homepage/set/item_fav_sort_order [post]
|
||||||
|
func SetItemFavSortOrder(c *gin.Context) {
|
||||||
|
var params HomepageOverrideItemFavSortOrderParams
|
||||||
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overrides := homepage.GetOverrideConfig()
|
||||||
|
overrides.SetFavSortOrder(params.Which, params.Value)
|
||||||
c.JSON(http.StatusOK, apitypes.Success("success"))
|
c.JSON(http.StatusOK, apitypes.Success("success"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,15 +208,8 @@ func SetItemVisible(c *gin.Context) {
|
|||||||
func SetCategoryOrder(c *gin.Context) {
|
func SetCategoryOrder(c *gin.Context) {
|
||||||
var params HomepageOverrideCategoryOrderParams
|
var params HomepageOverrideCategoryOrderParams
|
||||||
if err := c.ShouldBindJSON(¶ms); err != nil {
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
data, derr := c.GetRawData()
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
if derr != nil {
|
return
|
||||||
c.Error(apitypes.InternalServerError(derr, "failed to get raw data"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if uerr := json.Unmarshal(data, ¶ms); uerr != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", uerr))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
overrides := homepage.GetOverrideConfig()
|
overrides := homepage.GetOverrideConfig()
|
||||||
overrides.SetCategoryOrder(params.Which, params.Value)
|
overrides.SetCategoryOrder(params.Which, params.Value)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ func FromDocker(c *container.Summary, dockerHost string) (res *types.Container)
|
|||||||
IsExplicit: isExplicit,
|
IsExplicit: isExplicit,
|
||||||
IsHostNetworkMode: c.HostConfig.NetworkMode == "host",
|
IsHostNetworkMode: c.HostConfig.NetworkMode == "host",
|
||||||
Running: c.Status == "running" || c.State == "running",
|
Running: c.Status == "running" || c.State == "running",
|
||||||
|
State: c.State,
|
||||||
}
|
}
|
||||||
|
|
||||||
if agent.IsDockerHostAgent(dockerHost) {
|
if agent.IsDockerHostAgent(dockerHost) {
|
||||||
@@ -143,9 +144,11 @@ var databaseMPs = map[string]struct{}{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isDatabase(c *types.Container) bool {
|
func isDatabase(c *types.Container) bool {
|
||||||
for _, m := range c.Mounts.Iter {
|
if c.Mounts != nil { // only happens in test
|
||||||
if _, ok := databaseMPs[m]; ok {
|
for _, m := range c.Mounts.Iter {
|
||||||
return true
|
if _, ok := databaseMPs[m]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,34 +2,72 @@ package homepage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yusing/ds/ordered"
|
||||||
"github.com/yusing/go-proxy/internal/homepage/widgets"
|
"github.com/yusing/go-proxy/internal/homepage/widgets"
|
||||||
"github.com/yusing/go-proxy/internal/serialization"
|
"github.com/yusing/go-proxy/internal/serialization"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Homepage map[string]Category // @name HomepageItems
|
HomepageMap struct {
|
||||||
Category []*Item // @name HomepageCategory
|
ordered.Map[string, *Category]
|
||||||
|
} // @name HomepageItemsMap
|
||||||
|
|
||||||
|
Homepage []*Category // @name HomepageItems
|
||||||
|
Category struct {
|
||||||
|
Items []*Item `json:"items"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} // @name HomepageCategory
|
||||||
|
|
||||||
ItemConfig struct {
|
ItemConfig struct {
|
||||||
Show bool `json:"show"`
|
Show bool `json:"show"`
|
||||||
Name string `json:"name"` // display name
|
Name string `json:"name"` // display name
|
||||||
Icon *IconURL `json:"icon" swaggertype:"string"`
|
Icon *IconURL `json:"icon" swaggertype:"string"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category" validate:"omitempty"`
|
||||||
Description string `json:"description" aliases:"desc"`
|
Description string `json:"description" aliases:"desc"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
SortOrder int `json:"sort_order"`
|
Favorite bool `json:"favorite"`
|
||||||
}
|
|
||||||
|
|
||||||
Item struct {
|
|
||||||
*ItemConfig
|
|
||||||
|
|
||||||
WidgetConfig *widgets.Config `json:"widget_config,omitempty" aliases:"widget" extensions:"x-nullable"`
|
WidgetConfig *widgets.Config `json:"widget_config,omitempty" aliases:"widget" extensions:"x-nullable"`
|
||||||
|
} // @name HomepageItemConfig
|
||||||
|
|
||||||
Alias string `json:"alias"`
|
Widget struct {
|
||||||
Provider string `json:"provider"`
|
Label string `json:"label"`
|
||||||
OriginURL string `json:"origin_url"`
|
Value string `json:"value"`
|
||||||
}
|
} // @name HomepageItemWidget
|
||||||
|
|
||||||
|
Item struct {
|
||||||
|
ItemConfig
|
||||||
|
|
||||||
|
SortOrder int `json:"sort_order"` // sort order in category
|
||||||
|
FavSortOrder int `json:"fav_sort_order"` // sort order in favorite
|
||||||
|
AllSortOrder int `json:"all_sort_order"` // sort order in all
|
||||||
|
|
||||||
|
Clicks int `json:"clicks"`
|
||||||
|
|
||||||
|
Widgets []Widget `json:"widgets,omitempty"`
|
||||||
|
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
OriginURL string `json:"origin_url"`
|
||||||
|
ContainerID string `json:"container_id,omitempty" extensions:"x-nullable"`
|
||||||
|
} // @name HomepageItem
|
||||||
|
|
||||||
|
SortMethod string // @name HomepageSortMethod
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CategoryAll = "All"
|
||||||
|
CategoryFavorites = "Favorites"
|
||||||
|
CategoryHidden = "Hidden"
|
||||||
|
CategoryOthers = "Others"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SortMethodClicks = "clicks" // @name HomepageSortMethodClicks
|
||||||
|
SortMethodAlphabetical = "alphabetical" // @name HomepageSortMethodAlphabetical
|
||||||
|
SortMethodCustom = "custom" // @name HomepageSortMethodCustom
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -40,22 +78,115 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *ItemConfig) GetOverride(alias string) *ItemConfig {
|
func NewHomepageMap(total int) *HomepageMap {
|
||||||
return overrideConfigInstance.GetOverride(alias, cfg)
|
m := &HomepageMap{
|
||||||
|
Map: *ordered.NewMap[string, *Category](ordered.WithCapacity(10)),
|
||||||
|
}
|
||||||
|
m.Set(CategoryFavorites, &Category{
|
||||||
|
Items: make([]*Item, 0), // no capacity reserved for this category
|
||||||
|
Name: CategoryFavorites,
|
||||||
|
})
|
||||||
|
m.Set(CategoryAll, &Category{
|
||||||
|
Items: make([]*Item, 0, total),
|
||||||
|
Name: CategoryAll,
|
||||||
|
})
|
||||||
|
m.Set(CategoryHidden, &Category{
|
||||||
|
Items: make([]*Item, 0),
|
||||||
|
Name: CategoryHidden,
|
||||||
|
})
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Homepage) Add(item *Item) {
|
func (cfg Item) GetOverride() Item {
|
||||||
if c[item.Category] == nil {
|
return overrideConfigInstance.GetOverride(cfg)
|
||||||
c[item.Category] = make(Category, 0)
|
}
|
||||||
|
|
||||||
|
func (c *HomepageMap) Add(item *Item) {
|
||||||
|
c.add(item, item.Category)
|
||||||
|
// add to all category even if item is hidden
|
||||||
|
c.add(item, CategoryAll)
|
||||||
|
if item.Show {
|
||||||
|
if item.Favorite {
|
||||||
|
c.add(item, CategoryFavorites)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.add(item, CategoryHidden)
|
||||||
}
|
}
|
||||||
c[item.Category] = append(c[item.Category], item)
|
}
|
||||||
slices.SortStableFunc(c[item.Category], func(a, b *Item) int {
|
|
||||||
if a.SortOrder < b.SortOrder {
|
func (c *HomepageMap) add(item *Item, categoryName string) {
|
||||||
|
category := c.Get(categoryName)
|
||||||
|
if category == nil {
|
||||||
|
category = &Category{
|
||||||
|
Items: make([]*Item, 0),
|
||||||
|
Name: categoryName,
|
||||||
|
}
|
||||||
|
c.Set(categoryName, category)
|
||||||
|
}
|
||||||
|
category.Items = append(category.Items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) Sort(method SortMethod) {
|
||||||
|
switch method {
|
||||||
|
case SortMethodClicks:
|
||||||
|
c.sortByClicks()
|
||||||
|
case SortMethodAlphabetical:
|
||||||
|
c.sortByAlphabetical()
|
||||||
|
case SortMethodCustom:
|
||||||
|
c.sortByCustom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) sortByClicks() {
|
||||||
|
slices.SortStableFunc(c.Items, func(a, b *Item) int {
|
||||||
|
if a.Clicks > b.Clicks {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
if a.SortOrder > b.SortOrder {
|
if a.Clicks < b.Clicks {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
// fallback to alphabetical
|
||||||
|
return strings.Compare(a.Name, b.Name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Category) sortByAlphabetical() {
|
||||||
|
slices.SortStableFunc(c.Items, func(a, b *Item) int {
|
||||||
|
return strings.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) sortByCustom() {
|
||||||
|
switch c.Name {
|
||||||
|
case CategoryFavorites:
|
||||||
|
slices.SortStableFunc(c.Items, func(a, b *Item) int {
|
||||||
|
if a.FavSortOrder < b.FavSortOrder {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.FavSortOrder > b.FavSortOrder {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
case CategoryAll:
|
||||||
|
slices.SortStableFunc(c.Items, func(a, b *Item) int {
|
||||||
|
if a.AllSortOrder < b.AllSortOrder {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.AllSortOrder > b.AllSortOrder {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
slices.SortStableFunc(c.Items, func(a, b *Item) int {
|
||||||
|
if a.SortOrder < b.SortOrder {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.SortOrder > b.SortOrder {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
func TestOverrideItem(t *testing.T) {
|
func TestOverrideItem(t *testing.T) {
|
||||||
a := &Item{
|
a := &Item{
|
||||||
Alias: "foo",
|
Alias: "foo",
|
||||||
ItemConfig: &ItemConfig{
|
ItemConfig: ItemConfig{
|
||||||
Show: false,
|
Show: false,
|
||||||
Name: "Foo",
|
Name: "Foo",
|
||||||
Icon: &IconURL{
|
Icon: &IconURL{
|
||||||
@@ -20,7 +20,7 @@ func TestOverrideItem(t *testing.T) {
|
|||||||
Category: "App",
|
Category: "App",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
want := &ItemConfig{
|
want := ItemConfig{
|
||||||
Show: true,
|
Show: true,
|
||||||
Name: "Bar",
|
Name: "Bar",
|
||||||
Category: "Test",
|
Category: "Test",
|
||||||
@@ -30,7 +30,107 @@ func TestOverrideItem(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
overrides := GetOverrideConfig()
|
overrides := GetOverrideConfig()
|
||||||
|
overrides.Initialize()
|
||||||
overrides.OverrideItem(a.Alias, want)
|
overrides.OverrideItem(a.Alias, want)
|
||||||
got := a.GetOverride(a.Alias)
|
got := a.GetOverride()
|
||||||
ExpectEqual(t, got, want)
|
ExpectEqual(t, got, Item{
|
||||||
|
ItemConfig: want,
|
||||||
|
Alias: a.Alias,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverrideItem_PreservesURL(t *testing.T) {
|
||||||
|
a := &Item{
|
||||||
|
Alias: "svc",
|
||||||
|
ItemConfig: ItemConfig{
|
||||||
|
Show: true,
|
||||||
|
Name: "Service",
|
||||||
|
URL: "http://origin.local",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wantCfg := ItemConfig{
|
||||||
|
Show: true,
|
||||||
|
Name: "Overridden",
|
||||||
|
URL: "http://should-not-apply",
|
||||||
|
}
|
||||||
|
overrides := GetOverrideConfig()
|
||||||
|
overrides.Initialize()
|
||||||
|
overrides.OverrideItem(a.Alias, wantCfg)
|
||||||
|
|
||||||
|
got := a.GetOverride()
|
||||||
|
ExpectEqual(t, got.URL, "http://origin.local")
|
||||||
|
ExpectEqual(t, got.Name, "Overridden")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVisibilityFavoriteAndSortOrders(t *testing.T) {
|
||||||
|
a := &Item{
|
||||||
|
Alias: "alpha",
|
||||||
|
ItemConfig: ItemConfig{
|
||||||
|
Show: true,
|
||||||
|
Name: "Alpha",
|
||||||
|
Category: "Apps",
|
||||||
|
Favorite: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
overrides := GetOverrideConfig()
|
||||||
|
overrides.Initialize()
|
||||||
|
overrides.SetItemsVisibility([]string{a.Alias}, false)
|
||||||
|
overrides.SetItemsFavorite([]string{a.Alias}, true)
|
||||||
|
overrides.SetSortOrder(a.Alias, 5)
|
||||||
|
overrides.SetAllSortOrder(a.Alias, 9)
|
||||||
|
overrides.SetFavSortOrder(a.Alias, 2)
|
||||||
|
|
||||||
|
got := a.GetOverride()
|
||||||
|
ExpectEqual(t, got.Show, false)
|
||||||
|
ExpectEqual(t, got.Favorite, true)
|
||||||
|
ExpectEqual(t, got.SortOrder, 5)
|
||||||
|
ExpectEqual(t, got.AllSortOrder, 9)
|
||||||
|
ExpectEqual(t, got.FavSortOrder, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCategoryDefaultedWhenEmpty(t *testing.T) {
|
||||||
|
a := &Item{
|
||||||
|
Alias: "no-cat",
|
||||||
|
ItemConfig: ItemConfig{
|
||||||
|
Show: true,
|
||||||
|
Name: "NoCat",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
got := a.GetOverride()
|
||||||
|
ExpectEqual(t, got.Category, CategoryOthers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverrideItems_Bulk(t *testing.T) {
|
||||||
|
a := &Item{
|
||||||
|
Alias: "bulk-1",
|
||||||
|
ItemConfig: ItemConfig{
|
||||||
|
Show: true,
|
||||||
|
Name: "Bulk1",
|
||||||
|
Category: "X",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b := &Item{
|
||||||
|
Alias: "bulk-2",
|
||||||
|
ItemConfig: ItemConfig{
|
||||||
|
Show: true,
|
||||||
|
Name: "Bulk2",
|
||||||
|
Category: "Y",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
overrides := GetOverrideConfig()
|
||||||
|
overrides.Initialize()
|
||||||
|
overrides.OverrideItems(map[string]ItemConfig{
|
||||||
|
a.Alias: {Show: true, Name: "A*", Category: "AX"},
|
||||||
|
b.Alias: {Show: false, Name: "B*", Category: "BY"},
|
||||||
|
})
|
||||||
|
|
||||||
|
ga := a.GetOverride()
|
||||||
|
gb := b.GetOverride()
|
||||||
|
|
||||||
|
ExpectEqual(t, ga.Name, "A*")
|
||||||
|
ExpectEqual(t, ga.Category, "AX")
|
||||||
|
ExpectEqual(t, gb.Name, "B*")
|
||||||
|
ExpectEqual(t, gb.Category, "BY")
|
||||||
|
ExpectEqual(t, gb.Show, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ func NewIconKey(source IconSource, reference string) IconKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k IconKey) SourceRef() (IconSource, string) {
|
func (k IconKey) SourceRef() (IconSource, string) {
|
||||||
parts := strings.Split(string(k), "/")
|
source, ref, _ := strings.Cut(string(k), "/")
|
||||||
return IconSource(parts[0]), parts[1]
|
return IconSource(source), ref
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitIconListCache() {
|
func InitIconListCache() {
|
||||||
@@ -120,6 +120,10 @@ func InitIconListCache() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClearIconsCache() {
|
||||||
|
clear(iconsCache.Icons)
|
||||||
|
}
|
||||||
|
|
||||||
func ListAvailableIcons() (*Cache, error) {
|
func ListAvailableIcons() (*Cache, error) {
|
||||||
if common.IsTest {
|
if common.IsTest {
|
||||||
return iconsCache, nil
|
return iconsCache, nil
|
||||||
@@ -384,7 +388,8 @@ func UpdateSelfhstIcons() error {
|
|||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
var tag string
|
var tag string
|
||||||
if item.Tags != "" {
|
if item.Tags != "" {
|
||||||
tag = strutils.CommaSeperatedList(item.Tags)[0]
|
tag, _, _ = strings.Cut(item.Tags, ",")
|
||||||
|
tag = strings.TrimSpace(tag)
|
||||||
}
|
}
|
||||||
icon := &IconMeta{
|
icon := &IconMeta{
|
||||||
DisplayName: item.Name,
|
DisplayName: item.Name,
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ func runTests(t *testing.T, iconsCache *Cache, test []testCases) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListWalkxCodeIcons(t *testing.T) {
|
func TestListWalkxCodeIcons(t *testing.T) {
|
||||||
|
t.Cleanup(TestClearIconsCache)
|
||||||
|
|
||||||
MockHTTPGet([]byte(walkxcodeIcons))
|
MockHTTPGet([]byte(walkxcodeIcons))
|
||||||
if err := UpdateWalkxCodeIcons(); err != nil {
|
if err := UpdateWalkxCodeIcons(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -124,6 +126,7 @@ func TestListWalkxCodeIcons(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListSelfhstIcons(t *testing.T) {
|
func TestListSelfhstIcons(t *testing.T) {
|
||||||
|
t.Cleanup(TestClearIconsCache)
|
||||||
MockHTTPGet([]byte(selfhstIcons))
|
MockHTTPGet([]byte(selfhstIcons))
|
||||||
if err := UpdateSelfhstIcons(); err != nil {
|
if err := UpdateSelfhstIcons(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -135,9 +138,6 @@ func TestListSelfhstIcons(t *testing.T) {
|
|||||||
if len(iconsCache.Icons) != 3 {
|
if len(iconsCache.Icons) != 3 {
|
||||||
t.Fatalf("expect 3 icons, got %d", len(iconsCache.Icons))
|
t.Fatalf("expect 3 icons, got %d", len(iconsCache.Icons))
|
||||||
}
|
}
|
||||||
// if len(iconsCache.IconList) != 8 {
|
|
||||||
// t.Fatalf("expect 8 icons, got %d", len(iconsCache.IconList))
|
|
||||||
// }
|
|
||||||
test := []testCases{
|
test := []testCases{
|
||||||
{
|
{
|
||||||
Key: NewIconKey(IconSourceSelfhSt, "2fauth"),
|
Key: NewIconKey(IconSourceSelfhSt, "2fauth"),
|
||||||
|
|||||||
@@ -9,10 +9,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type OverrideConfig struct {
|
type OverrideConfig struct {
|
||||||
ItemOverrides map[string]*ItemConfig `json:"item_overrides"`
|
ItemOverrides map[string]ItemConfig `json:"item_overrides"`
|
||||||
DisplayOrder map[string]int `json:"display_order"`
|
DisplayOrder map[string]int `json:"display_order"`
|
||||||
CategoryOrder map[string]int `json:"category_order"`
|
CategoryOrder map[string]int `json:"category_order"`
|
||||||
ItemVisibility map[string]bool `json:"item_visibility"`
|
AllSortOrder map[string]int `json:"all_sort_order"`
|
||||||
|
FavSortOrder map[string]int `json:"fav_sort_order"`
|
||||||
|
ItemClicks map[string]int `json:"item_clicks"`
|
||||||
|
ItemVisibility map[string]bool `json:"item_visibility"`
|
||||||
|
ItemFavorite map[string]bool `json:"item_favorite"`
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,57 +27,104 @@ func GetOverrideConfig() *OverrideConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) Initialize() {
|
func (c *OverrideConfig) Initialize() {
|
||||||
c.ItemOverrides = make(map[string]*ItemConfig)
|
c.ItemOverrides = make(map[string]ItemConfig)
|
||||||
c.DisplayOrder = make(map[string]int)
|
c.DisplayOrder = make(map[string]int)
|
||||||
c.CategoryOrder = make(map[string]int)
|
c.CategoryOrder = make(map[string]int)
|
||||||
|
c.AllSortOrder = make(map[string]int)
|
||||||
|
c.FavSortOrder = make(map[string]int)
|
||||||
|
c.ItemClicks = make(map[string]int)
|
||||||
c.ItemVisibility = make(map[string]bool)
|
c.ItemVisibility = make(map[string]bool)
|
||||||
|
c.ItemFavorite = make(map[string]bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
|
func (c *OverrideConfig) OverrideItem(alias string, override ItemConfig) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
c.ItemOverrides[alias] = override
|
c.ItemOverrides[alias] = override
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) OverrideItems(items map[string]*ItemConfig) {
|
func (c *OverrideConfig) OverrideItems(items map[string]ItemConfig) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
maps.Copy(c.ItemOverrides, items)
|
maps.Copy(c.ItemOverrides, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) GetOverride(alias string, item *ItemConfig) *ItemConfig {
|
func (c *OverrideConfig) GetOverride(item Item) Item {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
if itemOverride, hasOverride := c.ItemOverrides[alias]; hasOverride {
|
|
||||||
itemOverride.URL = item.URL // NOTE: we don't want to override the URL
|
if overrides, hasOverride := c.ItemOverrides[item.Alias]; hasOverride {
|
||||||
item = itemOverride
|
overrides.URL = item.URL // NOTE: we don't want to override the URL
|
||||||
|
item.ItemConfig = overrides
|
||||||
}
|
}
|
||||||
if show, ok := c.ItemVisibility[alias]; ok {
|
|
||||||
clone := *item
|
if show, ok := c.ItemVisibility[item.Alias]; ok {
|
||||||
clone.Show = show
|
item.Show = show
|
||||||
return &clone
|
}
|
||||||
|
if fav, ok := c.ItemFavorite[item.Alias]; ok {
|
||||||
|
item.Favorite = fav
|
||||||
|
}
|
||||||
|
if displayOrder, ok := c.DisplayOrder[item.Alias]; ok {
|
||||||
|
item.SortOrder = displayOrder
|
||||||
|
}
|
||||||
|
if allSortOrder, ok := c.AllSortOrder[item.Alias]; ok {
|
||||||
|
item.AllSortOrder = allSortOrder
|
||||||
|
}
|
||||||
|
if favSortOrder, ok := c.FavSortOrder[item.Alias]; ok {
|
||||||
|
item.FavSortOrder = favSortOrder
|
||||||
|
}
|
||||||
|
if clicks, ok := c.ItemClicks[item.Alias]; ok {
|
||||||
|
item.Clicks = clicks
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Category == "" {
|
||||||
|
item.Category = CategoryOthers
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OverrideConfig) SetSortOrder(key string, value int) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.DisplayOrder[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OverrideConfig) SetAllSortOrder(key string, value int) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.AllSortOrder[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OverrideConfig) SetFavSortOrder(key string, value int) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.FavSortOrder[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OverrideConfig) SetItemsVisibility(keys []string, value bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
for _, key := range keys {
|
||||||
|
c.ItemVisibility[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OverrideConfig) SetItemsFavorite(keys []string, value bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
for _, key := range keys {
|
||||||
|
c.ItemFavorite[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) SetCategoryOrder(key string, value int) {
|
func (c *OverrideConfig) SetCategoryOrder(key string, value int) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
c.CategoryOrder[key] = value
|
c.CategoryOrder[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) UnhideItems(keys []string) {
|
func (c *OverrideConfig) IncrementItemClicks(key string) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
for _, key := range keys {
|
c.ItemClicks[key]++
|
||||||
c.ItemVisibility[key] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *OverrideConfig) HideItems(keys []string) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
for _, key := range keys {
|
|
||||||
c.ItemVisibility[key] = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type (
|
|||||||
ContainerName string `json:"container_name"`
|
ContainerName string `json:"container_name"`
|
||||||
ContainerID string `json:"container_id"`
|
ContainerID string `json:"container_id"`
|
||||||
|
|
||||||
|
State container.ContainerState `json:"state"`
|
||||||
|
|
||||||
Agent *agent.AgentConfig `json:"agent"`
|
Agent *agent.AgentConfig `json:"agent"`
|
||||||
|
|
||||||
Labels map[string]string `json:"-"` // for creating routes
|
Labels map[string]string `json:"-"` // for creating routes
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ type (
|
|||||||
IdlewatcherConfig() *IdlewatcherConfig
|
IdlewatcherConfig() *IdlewatcherConfig
|
||||||
HealthCheckConfig() *HealthCheckConfig
|
HealthCheckConfig() *HealthCheckConfig
|
||||||
LoadBalanceConfig() *LoadBalancerConfig
|
LoadBalanceConfig() *LoadBalancerConfig
|
||||||
HomepageConfig() *homepage.ItemConfig
|
HomepageItem() homepage.Item
|
||||||
HomepageItem() *homepage.Item
|
DisplayName() string
|
||||||
ContainerInfo() *Container
|
ContainerInfo() *Container
|
||||||
|
|
||||||
GetAgent() *agent.AgentConfig
|
GetAgent() *agent.AgentConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user