mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-21 00:29:03 +01:00
feat(homepage): implement SearchRoute method and enhance item configuration with sorting and visibility features, introduce All and Favorite categories
This commit is contained in:
@@ -25,6 +25,15 @@ func (cfg *Config) RouteProviderList() []config.RouteProviderListResponse {
|
||||
return list
|
||||
}
|
||||
|
||||
func (cfg *Config) SearchRoute(alias string) types.Route {
|
||||
for _, p := range cfg.providers.Range {
|
||||
if r, ok := p.GetRoute(alias); ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) Statistics() map[string]any {
|
||||
var rps, streams types.RouteStats
|
||||
var total uint16
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/notif"
|
||||
"github.com/yusing/go-proxy/internal/proxmox"
|
||||
"github.com/yusing/go-proxy/internal/serialization"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -51,6 +52,7 @@ type (
|
||||
Reload() gperr.Error
|
||||
Statistics() map[string]any
|
||||
RouteProviderList() []RouteProviderListResponse
|
||||
SearchRoute(alias string) types.Route
|
||||
Context() context.Context
|
||||
VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, gperr.Error)
|
||||
AutoCertProvider() *autocert.Provider
|
||||
|
||||
@@ -3,33 +3,59 @@ package homepage
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/yusing/ds/ordered"
|
||||
"github.com/yusing/go-proxy/internal/homepage/widgets"
|
||||
"github.com/yusing/go-proxy/internal/serialization"
|
||||
)
|
||||
|
||||
type (
|
||||
Homepage map[string]Category // @name HomepageItems
|
||||
Category []*Item // @name HomepageCategory
|
||||
HomepageMap struct {
|
||||
*ordered.Map[string, *Category]
|
||||
} // @name HomepageItemsMap
|
||||
|
||||
Homepage []*Category // @name HomepageItems
|
||||
Category struct {
|
||||
Items []*Item `json:"items"`
|
||||
Name string `json:"name"`
|
||||
} // @name HomepageCategory
|
||||
|
||||
ItemConfig struct {
|
||||
Show bool `json:"show"`
|
||||
Name string `json:"name"` // display name
|
||||
Icon *IconURL `json:"icon" swaggertype:"string"`
|
||||
Category string `json:"category"`
|
||||
Category string `json:"category" validate:"omitempty"`
|
||||
Description string `json:"description" aliases:"desc"`
|
||||
URL string `json:"url,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
Item struct {
|
||||
*ItemConfig
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
WidgetConfig *widgets.Config `json:"widget_config,omitempty" aliases:"widget" extensions:"x-nullable"`
|
||||
} // @name HomepageItemConfig
|
||||
|
||||
Widget struct {
|
||||
Label string `json:"label"`
|
||||
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
|
||||
|
||||
Widgets []Widget `json:"widgets,omitempty"`
|
||||
|
||||
Alias string `json:"alias"`
|
||||
Provider string `json:"provider"`
|
||||
OriginURL string `json:"origin_url"`
|
||||
}
|
||||
} // @name HomepageItem
|
||||
)
|
||||
|
||||
const (
|
||||
CategoryAll = "All"
|
||||
CategoryFavorites = "Favorites"
|
||||
CategoryHidden = "Hidden"
|
||||
CategoryOthers = "Others"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -40,22 +66,85 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func (cfg *ItemConfig) GetOverride(alias string) *ItemConfig {
|
||||
return overrideConfigInstance.GetOverride(alias, cfg)
|
||||
func NewHomepageMap(total int) *HomepageMap {
|
||||
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) {
|
||||
if c[item.Category] == nil {
|
||||
c[item.Category] = make(Category, 0)
|
||||
}
|
||||
c[item.Category] = append(c[item.Category], item)
|
||||
slices.SortStableFunc(c[item.Category], func(a, b *Item) int {
|
||||
if a.SortOrder < b.SortOrder {
|
||||
return -1
|
||||
}
|
||||
if a.SortOrder > b.SortOrder {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
func (cfg Item) GetOverride() Item {
|
||||
return overrideConfigInstance.GetOverride(cfg)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
a := &Item{
|
||||
Alias: "foo",
|
||||
ItemConfig: &ItemConfig{
|
||||
ItemConfig: ItemConfig{
|
||||
Show: false,
|
||||
Name: "Foo",
|
||||
Icon: &IconURL{
|
||||
@@ -20,7 +20,7 @@ func TestOverrideItem(t *testing.T) {
|
||||
Category: "App",
|
||||
},
|
||||
}
|
||||
want := &ItemConfig{
|
||||
want := ItemConfig{
|
||||
Show: true,
|
||||
Name: "Bar",
|
||||
Category: "Test",
|
||||
@@ -30,7 +30,107 @@ func TestOverrideItem(t *testing.T) {
|
||||
},
|
||||
}
|
||||
overrides := GetOverrideConfig()
|
||||
overrides.Initialize()
|
||||
overrides.OverrideItem(a.Alias, want)
|
||||
got := a.GetOverride(a.Alias)
|
||||
ExpectEqual(t, got, want)
|
||||
got := a.GetOverride()
|
||||
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) {
|
||||
parts := strings.Split(string(k), "/")
|
||||
return IconSource(parts[0]), parts[1]
|
||||
source, ref, _ := strings.Cut(string(k), "/")
|
||||
return IconSource(source), ref
|
||||
}
|
||||
|
||||
func InitIconListCache() {
|
||||
|
||||
@@ -9,10 +9,13 @@ import (
|
||||
)
|
||||
|
||||
type OverrideConfig struct {
|
||||
ItemOverrides map[string]*ItemConfig `json:"item_overrides"`
|
||||
DisplayOrder map[string]int `json:"display_order"`
|
||||
CategoryOrder map[string]int `json:"category_order"`
|
||||
ItemVisibility map[string]bool `json:"item_visibility"`
|
||||
ItemOverrides map[string]ItemConfig `json:"item_overrides"`
|
||||
DisplayOrder map[string]int `json:"display_order"`
|
||||
CategoryOrder map[string]int `json:"category_order"`
|
||||
AllSortOrder map[string]int `json:"all_sort_order"`
|
||||
FavSortOrder map[string]int `json:"fav_sort_order"`
|
||||
ItemVisibility map[string]bool `json:"item_visibility"`
|
||||
ItemFavorite map[string]bool `json:"item_favorite"`
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -23,57 +26,94 @@ func GetOverrideConfig() *OverrideConfig {
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) Initialize() {
|
||||
c.ItemOverrides = make(map[string]*ItemConfig)
|
||||
c.ItemOverrides = make(map[string]ItemConfig)
|
||||
c.DisplayOrder = make(map[string]int)
|
||||
c.CategoryOrder = make(map[string]int)
|
||||
c.AllSortOrder = make(map[string]int)
|
||||
c.FavSortOrder = make(map[string]int)
|
||||
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()
|
||||
defer c.mu.Unlock()
|
||||
c.ItemOverrides[alias] = override
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) OverrideItems(items map[string]*ItemConfig) {
|
||||
func (c *OverrideConfig) OverrideItems(items map[string]ItemConfig) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
maps.Copy(c.ItemOverrides, items)
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) GetOverride(alias string, item *ItemConfig) *ItemConfig {
|
||||
func (c *OverrideConfig) GetOverride(item Item) Item {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if itemOverride, hasOverride := c.ItemOverrides[alias]; hasOverride {
|
||||
itemOverride.URL = item.URL // NOTE: we don't want to override the URL
|
||||
item = itemOverride
|
||||
|
||||
if overrides, hasOverride := c.ItemOverrides[item.Alias]; hasOverride {
|
||||
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
|
||||
clone.Show = show
|
||||
return &clone
|
||||
|
||||
if show, ok := c.ItemVisibility[item.Alias]; ok {
|
||||
item.Show = show
|
||||
}
|
||||
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 item.Category == "" {
|
||||
item.Category = CategoryOthers
|
||||
}
|
||||
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) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.CategoryOrder[key] = value
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) UnhideItems(keys []string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for _, key := range keys {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ type (
|
||||
IdlewatcherConfig() *IdlewatcherConfig
|
||||
HealthCheckConfig() *HealthCheckConfig
|
||||
LoadBalanceConfig() *LoadBalancerConfig
|
||||
HomepageConfig() *homepage.ItemConfig
|
||||
HomepageItem() *homepage.Item
|
||||
HomepageItem() homepage.Item
|
||||
DisplayName() string
|
||||
ContainerInfo() *Container
|
||||
|
||||
GetAgent() *agent.AgentConfig
|
||||
|
||||
Reference in New Issue
Block a user