mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00
simplify icon caching and homepage item override
This commit is contained in:
parent
d429374924
commit
8b1a3a31ff
21 changed files with 395 additions and 331 deletions
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/yusing/go-proxy/internal"
|
||||
v1 "github.com/yusing/go-proxy/internal/api/v1"
|
||||
"github.com/yusing/go-proxy/internal/api/v1/auth"
|
||||
"github.com/yusing/go-proxy/internal/api/v1/favicon"
|
||||
"github.com/yusing/go-proxy/internal/api/v1/query"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/config"
|
||||
|
@ -32,6 +33,9 @@ func init() {
|
|||
out = io.MultiWriter(out, v1.MemLogger())
|
||||
}
|
||||
logging.InitLogger(out)
|
||||
internal.InitIconListCache()
|
||||
homepage.InitOverridesConfig()
|
||||
favicon.InitIconCache()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -97,8 +101,6 @@ func main() {
|
|||
}
|
||||
|
||||
middleware.LoadComposeFiles()
|
||||
internal.InitIconListCache()
|
||||
homepage.InitOverridesConfig()
|
||||
|
||||
var cfg *config.Config
|
||||
var err E.Error
|
||||
|
|
3
go.mod
3
go.mod
|
@ -14,8 +14,9 @@ require (
|
|||
github.com/gobwas/glob v0.2.3
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/gotify/server/v2 v2.6.1
|
||||
github.com/lithammer/fuzzysearch v1.1.8
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/vincent-petithory/dataurl v1.0.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
|
|
6
go.sum
6
go.sum
|
@ -92,6 +92,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
|
@ -130,8 +132,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
|||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.1 h1:wWXLKXwzpsduC3kUSahiL45MWxkGb+AQG0dsri4iftA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
|
|
|
@ -47,6 +47,10 @@ func NewHandler(cfg config.ConfigInstance) http.Handler {
|
|||
})
|
||||
mux.HandleFunc("GET,POST", "/v1/auth/callback", defaultAuth.LoginCallbackHandler)
|
||||
mux.HandleFunc("GET,POST", "/v1/auth/logout", auth.LogoutCallbackHandler(defaultAuth))
|
||||
} else {
|
||||
mux.HandleFunc("GET", "/v1/auth/check", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
return mux
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -17,13 +16,15 @@ import (
|
|||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/vincent-petithory/dataurl"
|
||||
"github.com/yusing/go-proxy/internal"
|
||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
)
|
||||
|
||||
type content struct {
|
||||
|
@ -80,17 +81,15 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
|
|||
var status int
|
||||
var errMsg string
|
||||
|
||||
hp := r.RawEntry().Homepage
|
||||
if hp != nil && hp.Icon != nil {
|
||||
hp := r.RawEntry().Homepage.GetOverride()
|
||||
if !hp.IsEmpty() && hp.Icon != nil {
|
||||
switch hp.Icon.IconSource {
|
||||
case homepage.IconSourceAbsolute:
|
||||
icon, status, errMsg = fetchIconAbsolute(hp.Icon.Value)
|
||||
case homepage.IconSourceRelative:
|
||||
icon, status, errMsg = findIcon(r, req, hp.Icon.Value)
|
||||
case homepage.IconSourceWalkXCode:
|
||||
icon, status, errMsg = fetchWalkxcodeIcon(hp.Icon.Extra.FileType, hp.Icon.Extra.Name)
|
||||
case homepage.IconSourceSelfhSt:
|
||||
icon, status, errMsg = fetchSelfhStIcon(hp.Icon.Extra.FileType, hp.Icon.Extra.Name)
|
||||
case homepage.IconSourceWalkXCode, homepage.IconSourceSelfhSt:
|
||||
icon, status, errMsg = fetchKnownIcon(hp.Icon)
|
||||
}
|
||||
} else {
|
||||
// try extract from "link[rel=icon]"
|
||||
|
@ -112,6 +111,24 @@ var (
|
|||
iconCacheMu sync.RWMutex
|
||||
)
|
||||
|
||||
func InitIconCache() {
|
||||
err := utils.LoadJSONIfExist(common.IconCachePath, &iconCache)
|
||||
if err != nil {
|
||||
logging.Error().Err(err).Msg("failed to load icon cache")
|
||||
} else {
|
||||
logging.Info().Msgf("icon cache loaded (%d icons)", len(iconCache))
|
||||
}
|
||||
|
||||
task.OnProgramExit("save_favicon_cache", func() {
|
||||
iconCacheMu.Lock()
|
||||
defer iconCacheMu.Unlock()
|
||||
|
||||
if err := utils.SaveJSON(common.IconCachePath, &iconCache, 0o644); err != nil {
|
||||
logging.Error().Err(err).Msg("failed to save icon cache")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func ResetIconCache(route route.HTTPRoute) {
|
||||
iconCacheMu.Lock()
|
||||
defer iconCacheMu.Unlock()
|
||||
|
@ -122,6 +139,11 @@ func loadIconCache(key string) (icon []byte, ok bool) {
|
|||
iconCacheMu.RLock()
|
||||
defer iconCacheMu.RUnlock()
|
||||
icon, ok = iconCache[key]
|
||||
if ok {
|
||||
logging.Debug().
|
||||
Str("key", key).
|
||||
Msg("icon found in cache")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -172,56 +194,30 @@ func sanitizeName(name string) string {
|
|||
return strings.ToLower(nameSanitizer.Replace(name))
|
||||
}
|
||||
|
||||
func fetchWalkxcodeIcon(filetype, name string) ([]byte, int, string) {
|
||||
func fetchKnownIcon(url *homepage.IconURL) ([]byte, int, string) {
|
||||
// if icon isn't in the list, no need to fetch
|
||||
if !internal.HasWalkxCodeIcon(name, filetype) {
|
||||
if !url.HasIcon() {
|
||||
logging.Debug().
|
||||
Str("filetype", filetype).
|
||||
Str("name", name).
|
||||
Msg("icon not found")
|
||||
return nil, http.StatusNotFound, "icon not found"
|
||||
Str("value", url.String()).
|
||||
Str("url", url.URL()).
|
||||
Msg("no such icon")
|
||||
return nil, http.StatusNotFound, "no such icon"
|
||||
}
|
||||
|
||||
icon, ok := loadIconCache("walkxcode/" + filetype + "/" + name)
|
||||
if ok {
|
||||
return icon, http.StatusOK, ""
|
||||
}
|
||||
|
||||
// url := homepage.DashboardIconBaseURL + filetype + "/" + name + "." + filetype
|
||||
url := fmt.Sprintf("https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/%s/%s.%s", filetype, name, filetype)
|
||||
return fetchIconAbsolute(url)
|
||||
}
|
||||
|
||||
func fetchSelfhStIcon(filetype, reference string) ([]byte, int, string) {
|
||||
// if icon isn't in the list, no need to fetch
|
||||
if !internal.HasSelfhstIcon(reference, filetype) {
|
||||
logging.Debug().
|
||||
Str("filetype", filetype).
|
||||
Str("reference", reference).
|
||||
Msg("icon not found")
|
||||
return nil, http.StatusNotFound, "icon not found"
|
||||
}
|
||||
|
||||
icon, ok := loadIconCache("selfh.st/" + filetype + "/" + reference)
|
||||
if ok {
|
||||
return icon, http.StatusOK, ""
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://cdn.jsdelivr.net/gh/selfhst/icons/%s/%s.%s", filetype, reference, filetype)
|
||||
return fetchIconAbsolute(url)
|
||||
return fetchIconAbsolute(url.URL())
|
||||
}
|
||||
|
||||
func fetchIcon(filetype, filename string) (icon []byte, status int, errMsg string) {
|
||||
icon, status, errMsg = fetchSelfhStIcon(filetype, filename)
|
||||
icon, status, errMsg = fetchKnownIcon(homepage.NewSelfhStIconURL(filename, filetype))
|
||||
if icon != nil {
|
||||
return
|
||||
}
|
||||
icon, status, errMsg = fetchWalkxcodeIcon(filetype, filename)
|
||||
icon, status, errMsg = fetchKnownIcon(homepage.NewWalkXCodeIconURL(filename, filetype))
|
||||
return
|
||||
}
|
||||
|
||||
func findIcon(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, status int, errMsg string) {
|
||||
key := r.TargetName()
|
||||
key := r.RawEntry().Provider + ":" + r.TargetName()
|
||||
icon, ok := loadIconCache(key)
|
||||
if ok {
|
||||
if icon == nil {
|
||||
|
@ -239,8 +235,9 @@ func findIcon(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, st
|
|||
// fallback to parse html
|
||||
icon, status, errMsg = findIconSlow(r, req, uri)
|
||||
}
|
||||
// set even if error (nil)
|
||||
storeIconCache(key, icon)
|
||||
if icon != nil {
|
||||
storeIconCache(key, icon)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -4,19 +4,14 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
HomepageOverrideDisplayname = "display_name"
|
||||
HomepageOverrideDisplayOrder = "display_order"
|
||||
HomepageOverrideDisplayCategory = "display_category"
|
||||
HomepageOverrideCategoryOrder = "category_order"
|
||||
HomepageOverrideCategoryName = "category_name"
|
||||
HomepageOverrideIcon = "icon"
|
||||
HomepageOverrideShow = "show"
|
||||
HomepageOverrideItem = "item"
|
||||
HomepageOverrideCategoryOrder = "category_order"
|
||||
HomepageOverrideCategoryName = "category_name"
|
||||
)
|
||||
|
||||
func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -29,30 +24,27 @@ func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "missing value", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
overrides := homepage.GetJSONConfig()
|
||||
overrides := homepage.GetOverrideConfig()
|
||||
switch what {
|
||||
case HomepageOverrideDisplayname:
|
||||
utils.RespondError(w, overrides.SetDisplayNameOverride(which, value))
|
||||
case HomepageOverrideDisplayCategory:
|
||||
utils.RespondError(w, overrides.SetDisplayCategoryOverride(which, value))
|
||||
case HomepageOverrideItem:
|
||||
var override homepage.ItemConfig
|
||||
if err := utils.DeserializeJSON([]byte(value), &override); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
overrides.OverrideItem(which, &override)
|
||||
case HomepageOverrideCategoryName:
|
||||
utils.RespondError(w, overrides.SetCategoryNameOverride(which, value))
|
||||
case HomepageOverrideIcon:
|
||||
utils.RespondError(w, overrides.SetIconOverride(which, value))
|
||||
case HomepageOverrideShow:
|
||||
utils.RespondError(w, overrides.SetShowItemOverride(which, strutils.ParseBool(value)))
|
||||
case HomepageOverrideDisplayOrder, HomepageOverrideCategoryOrder:
|
||||
overrides.SetCategoryNameOverride(which, value)
|
||||
case HomepageOverrideCategoryOrder:
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid integer", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if what == HomepageOverrideDisplayOrder {
|
||||
utils.RespondError(w, overrides.SetDisplayOrder(which, v))
|
||||
} else {
|
||||
utils.RespondError(w, overrides.SetCategoryOrder(which, v))
|
||||
}
|
||||
overrides.SetCategoryOrder(which, v)
|
||||
default:
|
||||
http.Error(w, "invalid what", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package v1
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/yusing/go-proxy/internal"
|
||||
|
@ -61,7 +62,11 @@ func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
|
|||
case ListHomepageCategories:
|
||||
U.RespondJSON(w, r, routequery.HomepageCategories())
|
||||
case ListIcons:
|
||||
icons, err := internal.ListAvailableIcons()
|
||||
limit, err := strconv.Atoi(r.FormValue("limit"))
|
||||
if err != nil {
|
||||
limit = 0
|
||||
}
|
||||
icons, err := internal.SearchIcons(r.FormValue("keyword"), limit)
|
||||
if err != nil {
|
||||
U.RespondError(w, err)
|
||||
return
|
||||
|
|
|
@ -60,7 +60,7 @@ func PeriodicWS(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Reques
|
|||
return
|
||||
case <-ticker.C:
|
||||
if err := do(conn); err != nil {
|
||||
HandleErr(w, r, err)
|
||||
LogError(r).Msg(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ const (
|
|||
ConfigPath = ConfigBasePath + "/" + ConfigFileName
|
||||
HomepageJSONConfigPath = ConfigBasePath + "/.homepage.json"
|
||||
IconListCachePath = ConfigBasePath + "/.icon_list_cache.json"
|
||||
IconCachePath = ConfigBasePath + "/.icon_cache.json"
|
||||
|
||||
MiddlewareComposeBasePath = ConfigBasePath + "/middlewares"
|
||||
|
||||
|
|
|
@ -5,39 +5,45 @@ type (
|
|||
Config map[string]Category
|
||||
Category []*Item
|
||||
|
||||
Item struct {
|
||||
ItemConfig struct {
|
||||
Show bool `json:"show"`
|
||||
Name string `json:"name"` // display name
|
||||
Icon *IconURL `json:"icon"`
|
||||
URL string `json:"url"` // alias + domain
|
||||
Category string `json:"category"`
|
||||
Description string `json:"description" aliases:"desc"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
WidgetConfig map[string]any `json:"widget_config" aliases:"widget"`
|
||||
URL string `json:"url"` // alias + domain
|
||||
}
|
||||
|
||||
Item struct {
|
||||
*ItemConfig
|
||||
|
||||
Alias string `json:"alias"` // proxy alias
|
||||
SourceType string `json:"source_type"`
|
||||
AltURL string `json:"alt_url"` // original proxy target
|
||||
Provider string `json:"provider"`
|
||||
|
||||
IsUnset bool
|
||||
}
|
||||
)
|
||||
|
||||
func (item *Item) IsEmpty() bool {
|
||||
return item == nil || (item.Name == "" &&
|
||||
item.Icon == nil &&
|
||||
item.URL == "" &&
|
||||
item.Category == "" &&
|
||||
item.Description == "" &&
|
||||
len(item.WidgetConfig) == 0)
|
||||
func NewItem(alias string) *Item {
|
||||
return &Item{
|
||||
ItemConfig: &ItemConfig{
|
||||
Show: true,
|
||||
},
|
||||
Alias: alias,
|
||||
IsUnset: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (item *Item) GetOverriddenItem() *Item {
|
||||
overrides := GetJSONConfig()
|
||||
clone := *item
|
||||
clone.Name = overrides.GetDisplayName(item)
|
||||
clone.Icon = overrides.GetDisplayIcon(item)
|
||||
clone.Category = overrides.GetCategory(item)
|
||||
clone.Show = overrides.GetShowItem(item)
|
||||
return &clone
|
||||
func (item *Item) IsEmpty() bool {
|
||||
return item == nil || item.IsUnset || item.ItemConfig == nil
|
||||
}
|
||||
|
||||
func (item *Item) GetOverride() *Item {
|
||||
return overrideConfigInstance.GetOverride(item)
|
||||
}
|
||||
|
||||
func NewHomePageConfig() Config {
|
||||
|
|
|
@ -7,25 +7,25 @@ import (
|
|||
)
|
||||
|
||||
func TestOverrideItem(t *testing.T) {
|
||||
a := &Item{
|
||||
Show: false,
|
||||
Alias: "foo",
|
||||
Name: "Foo",
|
||||
Icon: &IconURL{
|
||||
Value: "/favicon.ico",
|
||||
IconSource: IconSourceRelative,
|
||||
},
|
||||
Category: "App",
|
||||
}
|
||||
overrides := GetJSONConfig()
|
||||
ExpectNoError(t, overrides.SetShowItemOverride(a.Alias, true))
|
||||
ExpectNoError(t, overrides.SetDisplayNameOverride(a.Alias, "Bar"))
|
||||
ExpectNoError(t, overrides.SetDisplayCategoryOverride(a.Alias, "Test"))
|
||||
ExpectNoError(t, overrides.SetIconOverride(a.Alias, "png/example.png"))
|
||||
// a := &Item{
|
||||
// Show: false,
|
||||
// Alias: "foo",
|
||||
// Name: "Foo",
|
||||
// Icon: &IconURL{
|
||||
// Value: "/favicon.ico",
|
||||
// IconSource: IconSourceRelative,
|
||||
// },
|
||||
// Category: "App",
|
||||
// }
|
||||
// overrides := GetJSONConfig()
|
||||
// overrides.SetShowItemOverride(a.Alias, true)
|
||||
// overrides.SetDisplayNameOverride(a.Alias, "Bar")
|
||||
// overrides.SetDisplayCategoryOverride(a.Alias, "Test")
|
||||
// ExpectNoError(t, overrides.SetIconOverride(a.Alias, "@walkxcode/example.png"))
|
||||
|
||||
overridden := a.GetOverriddenItem()
|
||||
ExpectTrue(t, overridden.Show)
|
||||
ExpectEqual(t, overridden.Name, "Bar")
|
||||
ExpectEqual(t, overridden.Category, "Test")
|
||||
ExpectEqual(t, overridden.Icon.String(), "png/example.png")
|
||||
// overridden := a.GetOverriddenItem()
|
||||
// ExpectTrue(t, overridden.Show)
|
||||
// ExpectEqual(t, overridden.Name, "Bar")
|
||||
// ExpectEqual(t, overridden.Category, "Test")
|
||||
// ExpectEqual(t, overridden.Icon.String(), "png/example.png")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package homepage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/yusing/go-proxy/internal"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
type (
|
||||
IconURL struct {
|
||||
Value string `json:"value"`
|
||||
FullValue string `json:"full_value"`
|
||||
IconSource `json:"source"`
|
||||
Extra *IconExtra `json:"extra"`
|
||||
}
|
||||
|
@ -31,14 +33,39 @@ const (
|
|||
|
||||
var ErrInvalidIconURL = E.New("invalid icon url")
|
||||
|
||||
func (u *IconURL) HasIcon() bool {
|
||||
if u.IconSource == IconSourceSelfhSt &&
|
||||
!internal.HasSelfhstIcon(u.Extra.Name, u.Extra.FileType) {
|
||||
return false
|
||||
func NewSelfhStIconURL(reference, format string) *IconURL {
|
||||
return &IconURL{
|
||||
Value: reference + "." + format,
|
||||
FullValue: fmt.Sprintf("@selfhst/%s.%s", reference, format),
|
||||
IconSource: IconSourceSelfhSt,
|
||||
Extra: &IconExtra{
|
||||
FileType: format,
|
||||
Name: reference,
|
||||
},
|
||||
}
|
||||
if u.IconSource == IconSourceWalkXCode &&
|
||||
!internal.HasWalkxCodeIcon(u.Extra.Name, u.Extra.FileType) {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewWalkXCodeIconURL(name, format string) *IconURL {
|
||||
return &IconURL{
|
||||
Value: name + "." + format,
|
||||
FullValue: fmt.Sprintf("@walkxcode/%s.%s", name, format),
|
||||
IconSource: IconSourceWalkXCode,
|
||||
Extra: &IconExtra{
|
||||
FileType: format,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HasIcon checks if the icon referenced by the IconURL exists in the cache based on its source.
|
||||
// Returns false if the icon does not exist for IconSourceSelfhSt or IconSourceWalkXCode,
|
||||
// otherwise returns true.
|
||||
func (u *IconURL) HasIcon() bool {
|
||||
if u.IconSource == IconSourceSelfhSt {
|
||||
return internal.HasSelfhstIcon(u.Extra.Name, u.Extra.FileType)
|
||||
}
|
||||
if u.IconSource == IconSourceWalkXCode {
|
||||
return internal.HasWalkxCodeIcon(u.Extra.Name, u.Extra.FileType)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -52,6 +79,7 @@ func (u *IconURL) Parse(v string) error {
|
|||
if slashIndex == -1 {
|
||||
return ErrInvalidIconURL
|
||||
}
|
||||
u.FullValue = v
|
||||
beforeSlash := v[:slashIndex]
|
||||
switch beforeSlash {
|
||||
case "http:", "https:":
|
||||
|
@ -63,19 +91,23 @@ func (u *IconURL) Parse(v string) error {
|
|||
if u.Value == "/" {
|
||||
return ErrInvalidIconURL.Withf("%s", "empty path")
|
||||
}
|
||||
case "png", "svg", "webp": // walkXCode Icons
|
||||
case "png", "svg", "webp": // walkxcode Icons
|
||||
u.Value = v
|
||||
u.IconSource = IconSourceWalkXCode
|
||||
u.Extra = &IconExtra{
|
||||
FileType: beforeSlash,
|
||||
Name: strings.TrimSuffix(v[slashIndex+1:], "."+beforeSlash),
|
||||
}
|
||||
case "@selfhst": // selfh.st Icons, @selfhst/<reference>.<format>
|
||||
u.Value = v[slashIndex:]
|
||||
u.IconSource = IconSourceSelfhSt
|
||||
parts := strings.Split(v[slashIndex+1:], ".")
|
||||
case "@selfhst", "@walkxcode": // selfh.st / walkxcode Icons, @selfhst/<reference>.<format>
|
||||
u.Value = v[slashIndex+1:]
|
||||
if beforeSlash == "@selfhst" {
|
||||
u.IconSource = IconSourceSelfhSt
|
||||
} else {
|
||||
u.IconSource = IconSourceWalkXCode
|
||||
}
|
||||
parts := strings.Split(u.Value, ".")
|
||||
if len(parts) != 2 {
|
||||
return ErrInvalidIconURL.Withf("%s", "expect @selfhst/<reference>.<format>, e.g. @selfhst/adguard-home.webp")
|
||||
return ErrInvalidIconURL.Withf("expect @%s/<reference>.<format>, e.g. @%s/adguard-home.webp", beforeSlash, beforeSlash)
|
||||
}
|
||||
reference, format := parts[0], strings.ToLower(parts[1])
|
||||
if reference == "" || format == "" {
|
||||
|
@ -84,7 +116,7 @@ func (u *IconURL) Parse(v string) error {
|
|||
switch format {
|
||||
case "svg", "png", "webp":
|
||||
default:
|
||||
return ErrInvalidIconURL.Withf("%s", "invalid format, expect svg/png/webp")
|
||||
return ErrInvalidIconURL.Withf("%s", "invalid image format, expect svg/png/webp")
|
||||
}
|
||||
u.Extra = &IconExtra{
|
||||
FileType: format,
|
||||
|
@ -99,11 +131,33 @@ func (u *IconURL) Parse(v string) error {
|
|||
}
|
||||
|
||||
if !u.HasIcon() {
|
||||
return ErrInvalidIconURL.Withf("no such icon %s", u.Value)
|
||||
return ErrInvalidIconURL.Withf("no such icon %s from %s", u.Value, beforeSlash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *IconURL) String() string {
|
||||
return u.Value
|
||||
func (u *IconURL) URL() string {
|
||||
switch u.IconSource {
|
||||
case IconSourceAbsolute:
|
||||
return u.Value
|
||||
case IconSourceRelative:
|
||||
return "/" + u.Value
|
||||
case IconSourceWalkXCode:
|
||||
return fmt.Sprintf("https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/%s/%s.%s", u.Extra.FileType, u.Extra.Name, u.Extra.FileType)
|
||||
case IconSourceSelfhSt:
|
||||
return fmt.Sprintf("https://cdn.jsdelivr.net/gh/selfhst/icons/%s/%s.%s", u.Extra.FileType, u.Extra.Name, u.Extra.FileType)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *IconURL) String() string {
|
||||
return u.FullValue
|
||||
}
|
||||
|
||||
func (u *IconURL) MarshalText() ([]byte, error) {
|
||||
return []byte(u.String()), nil
|
||||
}
|
||||
|
||||
func (u *IconURL) UnmarshalText(data []byte) error {
|
||||
return u.Parse(string(data))
|
||||
}
|
||||
|
|
|
@ -49,13 +49,25 @@ func TestIconURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "walkxcode",
|
||||
input: "png/walkxcode.png",
|
||||
input: "png/adguard-home.png",
|
||||
wantValue: &IconURL{
|
||||
Value: "png/walkxcode.png",
|
||||
Value: "png/adguard-home.png",
|
||||
IconSource: IconSourceWalkXCode,
|
||||
Extra: &IconExtra{
|
||||
FileType: "png",
|
||||
Name: "walkxcode",
|
||||
Name: "adguard-home",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "walkxcode_alt",
|
||||
input: "@walkxcode/adguard-home.png",
|
||||
wantValue: &IconURL{
|
||||
Value: "adguard-home.png",
|
||||
IconSource: IconSourceWalkXCode,
|
||||
Extra: &IconExtra{
|
||||
FileType: "png",
|
||||
Name: "adguard-home",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -66,13 +78,13 @@ func TestIconURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "selfh.st_valid",
|
||||
input: "@selfhst/foo.png",
|
||||
input: "@selfhst/adguard-home.png",
|
||||
wantValue: &IconURL{
|
||||
Value: "/foo.png",
|
||||
Value: "adguard-home.png",
|
||||
IconSource: IconSourceSelfhSt,
|
||||
Extra: &IconExtra{
|
||||
FileType: "png",
|
||||
Name: "foo",
|
||||
Name: "adguard-home",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
package homepage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
)
|
||||
|
||||
type JSONConfig struct {
|
||||
DisplayNameOverride map[string]string `json:"display_name_override"`
|
||||
DisplayCategoryOverride map[string]string `json:"display_category_override"`
|
||||
DisplayOrder map[string]int `json:"display_order"` // TODO: implement this
|
||||
CategoryNameOverride map[string]string `json:"category_name_override"`
|
||||
CategoryOrder map[string]int `json:"category_order"` // TODO: implement this
|
||||
IconOverride map[string]*IconURL `json:"icon_override"`
|
||||
ShowItemOverride map[string]bool `json:"show_item_override"`
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var jsonConfigInstance *JSONConfig
|
||||
|
||||
func InitOverridesConfig() {
|
||||
jsonConfigInstance = &JSONConfig{
|
||||
DisplayNameOverride: make(map[string]string),
|
||||
DisplayCategoryOverride: make(map[string]string),
|
||||
DisplayOrder: make(map[string]int),
|
||||
CategoryNameOverride: make(map[string]string),
|
||||
CategoryOrder: make(map[string]int),
|
||||
IconOverride: make(map[string]*IconURL),
|
||||
ShowItemOverride: make(map[string]bool),
|
||||
}
|
||||
err := utils.LoadJSON(common.HomepageJSONConfigPath, jsonConfigInstance)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logging.Fatal().Err(err).Msg("failed to load homepage overrides config")
|
||||
}
|
||||
}
|
||||
|
||||
func GetJSONConfig() *JSONConfig {
|
||||
return jsonConfigInstance
|
||||
}
|
||||
|
||||
func (c *JSONConfig) save() error {
|
||||
if common.IsTest {
|
||||
return nil
|
||||
}
|
||||
return utils.SaveJSON(common.HomepageJSONConfigPath, c, 0o644)
|
||||
}
|
||||
|
||||
func (c *JSONConfig) GetDisplayName(item *Item) string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if override, ok := c.DisplayNameOverride[item.Alias]; ok {
|
||||
return override
|
||||
}
|
||||
return item.Name
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetDisplayNameOverride(key, value string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.DisplayNameOverride[key] = value
|
||||
return c.save()
|
||||
}
|
||||
|
||||
func (c *JSONConfig) GetCategory(item *Item) string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
category := item.Category
|
||||
if override, ok := c.DisplayCategoryOverride[item.Alias]; ok {
|
||||
category = override
|
||||
}
|
||||
if override, ok := c.CategoryNameOverride[category]; ok {
|
||||
return override
|
||||
}
|
||||
return category
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetDisplayCategoryOverride(key, value string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.DisplayCategoryOverride[key] = value
|
||||
return c.save()
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetDisplayOrder(key string, value int) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.DisplayOrder[key] = value
|
||||
return c.save()
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetCategoryNameOverride(key, value string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.CategoryNameOverride[key] = value
|
||||
return c.save()
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetCategoryOrder(key string, value int) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.CategoryOrder[key] = value
|
||||
return c.save()
|
||||
}
|
||||
|
||||
func (c *JSONConfig) GetDisplayIcon(item *Item) *IconURL {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if override, ok := c.IconOverride[item.Alias]; ok {
|
||||
return override
|
||||
}
|
||||
return item.Icon
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetIconOverride(key, value string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
var url IconURL
|
||||
if err := url.Parse(value); err != nil {
|
||||
return err
|
||||
}
|
||||
if !url.HasIcon() {
|
||||
return errors.New("no such icon")
|
||||
}
|
||||
c.IconOverride[key] = &url
|
||||
return c.save()
|
||||
}
|
||||
|
||||
func (c *JSONConfig) GetShowItem(item *Item) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if override, ok := c.ShowItemOverride[item.Alias]; ok {
|
||||
return override
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *JSONConfig) SetShowItemOverride(key string, value bool) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.ShowItemOverride[key] = value
|
||||
return c.save()
|
||||
}
|
94
internal/homepage/override_config.go
Normal file
94
internal/homepage/override_config.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package homepage
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
)
|
||||
|
||||
type OverrideConfig struct {
|
||||
ItemOverrides map[string]*ItemConfig `json:"item_overrides"`
|
||||
DisplayOrder map[string]int `json:"display_order"` // TODO: implement this
|
||||
CategoryName map[string]string `json:"category_name"`
|
||||
CategoryOrder map[string]int `json:"category_order"` // TODO: implement this
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var overrideConfigInstance *OverrideConfig
|
||||
|
||||
func must(b []byte, err error) []byte {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func InitOverridesConfig() {
|
||||
overrideConfigInstance = &OverrideConfig{
|
||||
ItemOverrides: make(map[string]*ItemConfig),
|
||||
DisplayOrder: make(map[string]int),
|
||||
CategoryName: make(map[string]string),
|
||||
CategoryOrder: make(map[string]int),
|
||||
}
|
||||
err := utils.LoadJSONIfExist(common.HomepageJSONConfigPath, overrideConfigInstance)
|
||||
if err != nil {
|
||||
logging.Error().Err(err).Msg("failed to load homepage overrides config")
|
||||
} else {
|
||||
logging.Info().Msgf("homepage overrides config loaded, %d items", len(overrideConfigInstance.ItemOverrides))
|
||||
}
|
||||
task.OnProgramExit("save_homepage_json_config", func() {
|
||||
if err := utils.SaveJSON(common.HomepageJSONConfigPath, overrideConfigInstance, 0o644); err != nil {
|
||||
logging.Error().Err(err).Msg("failed to save homepage overrides config")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func GetOverrideConfig() *OverrideConfig {
|
||||
return overrideConfigInstance
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) UnmarshalJSON(data []byte) error {
|
||||
return utils.DeserializeJSON(data, c)
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.ItemOverrides[alias] = override
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) GetOverride(item *Item) *Item {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
itemOverride, ok := c.ItemOverrides[item.Alias]
|
||||
if !ok {
|
||||
if catOverride, ok := c.CategoryName[item.Category]; ok {
|
||||
clone := *item
|
||||
clone.Category = catOverride
|
||||
return &clone
|
||||
}
|
||||
return item
|
||||
} else {
|
||||
clone := *item
|
||||
clone.ItemConfig = itemOverride
|
||||
if catOverride, ok := c.CategoryName[clone.Category]; ok {
|
||||
clone.Category = catOverride
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) SetCategoryNameOverride(key, value string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.CategoryName[key] = value
|
||||
}
|
||||
|
||||
func (c *OverrideConfig) SetCategoryOrder(key string, value int) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.CategoryOrder[key] = value
|
||||
}
|
|
@ -8,9 +8,11 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
)
|
||||
|
||||
type GitHubContents struct { //! keep this, may reuse in future
|
||||
|
@ -23,25 +25,20 @@ type GitHubContents struct { //! keep this, may reuse in future
|
|||
|
||||
type (
|
||||
IconsMap map[string]map[string]struct{}
|
||||
IconList []string
|
||||
Cache struct {
|
||||
WalkxCode, Selfhst IconsMap
|
||||
DisplayNames ReferenceDisplayNameMap
|
||||
IconList IconList // combined into a single list
|
||||
}
|
||||
ReferenceDisplayNameMap map[string]string
|
||||
)
|
||||
|
||||
func (icons *Cache) isEmpty() bool {
|
||||
return len(icons.WalkxCode) == 0 && len(icons.Selfhst) == 0
|
||||
func (icons *Cache) needUpdate() bool {
|
||||
return len(icons.WalkxCode) == 0 || len(icons.Selfhst) == 0 || len(icons.IconList) == 0 || len(icons.DisplayNames) == 0
|
||||
}
|
||||
|
||||
func (icons *Cache) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"walkxcode": icons.WalkxCode,
|
||||
"selfhst": icons.Selfhst,
|
||||
})
|
||||
}
|
||||
|
||||
const updateInterval = 1 * time.Hour
|
||||
const updateInterval = 2 * time.Hour
|
||||
|
||||
var (
|
||||
iconsCache *Cache
|
||||
|
@ -59,16 +56,17 @@ func InitIconListCache() {
|
|||
WalkxCode: make(IconsMap),
|
||||
Selfhst: make(IconsMap),
|
||||
DisplayNames: make(ReferenceDisplayNameMap),
|
||||
IconList: []string{},
|
||||
}
|
||||
err := utils.LoadJSON(common.IconListCachePath, iconsCache)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logging.Fatal().Err(err).Msg("failed to load icon list cache config")
|
||||
} else if err == nil {
|
||||
if stats, err := os.Stat(common.IconListCachePath); err != nil {
|
||||
logging.Fatal().Err(err).Msg("failed to load icon list cache config")
|
||||
} else {
|
||||
lastUpdate = stats.ModTime()
|
||||
}
|
||||
err := utils.LoadJSONIfExist(common.IconListCachePath, iconsCache)
|
||||
if err != nil {
|
||||
logging.Error().Err(err).Msg("failed to load icon list cache config")
|
||||
} else if stats, err := os.Stat(common.IconListCachePath); err == nil {
|
||||
lastUpdate = stats.ModTime()
|
||||
logging.Info().Msgf("icon list cache loaded (%d icons, %d display names), last updated at %s",
|
||||
len(iconsCache.IconList),
|
||||
len(iconsCache.DisplayNames),
|
||||
strutils.FormatTime(lastUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +75,7 @@ func ListAvailableIcons() (*Cache, error) {
|
|||
defer iconsCahceMu.Unlock()
|
||||
|
||||
if time.Since(lastUpdate) < updateInterval {
|
||||
if !iconsCache.isEmpty() {
|
||||
if !iconsCache.needUpdate() {
|
||||
return iconsCache, nil
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +85,8 @@ func ListAvailableIcons() (*Cache, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
logging.Info().Msg("icons list updated")
|
||||
|
||||
iconsCache = icons
|
||||
lastUpdate = time.Now()
|
||||
|
||||
|
@ -97,6 +97,17 @@ func ListAvailableIcons() (*Cache, error) {
|
|||
return icons, nil
|
||||
}
|
||||
|
||||
func SearchIcons(keyword string, limit int) ([]string, error) {
|
||||
icons, err := ListAvailableIcons()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keyword == "" {
|
||||
return utils.Slice(icons.IconList, limit), nil
|
||||
}
|
||||
return utils.Slice(fuzzy.Find(keyword, icons.IconList), limit), nil
|
||||
}
|
||||
|
||||
func HasWalkxCodeIcon(name string, filetype string) bool {
|
||||
icons, err := ListAvailableIcons()
|
||||
if err != nil {
|
||||
|
@ -134,20 +145,26 @@ func GetDisplayName(reference string) (string, bool) {
|
|||
}
|
||||
|
||||
func fetchIconData() (*Cache, error) {
|
||||
walkxCodeIcons, err := fetchWalkxCodeIcons()
|
||||
walkxCodeIconMap, walkxCodeIconList, err := fetchWalkxCodeIcons()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selfhstIcons, referenceToNames, err := fetchSelfhstIcons()
|
||||
n := 0
|
||||
for _, items := range walkxCodeIconMap {
|
||||
n += len(items)
|
||||
}
|
||||
|
||||
selfhstIconMap, selfhstIconList, referenceToNames, err := fetchSelfhstIcons()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
WalkxCode: walkxCodeIcons,
|
||||
Selfhst: selfhstIcons,
|
||||
WalkxCode: walkxCodeIconMap,
|
||||
Selfhst: selfhstIconMap,
|
||||
DisplayNames: referenceToNames,
|
||||
IconList: append(walkxCodeIconList, selfhstIconList...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -166,35 +183,37 @@ format:
|
|||
]
|
||||
}
|
||||
*/
|
||||
func fetchWalkxCodeIcons() (IconsMap, error) {
|
||||
func fetchWalkxCodeIcons() (IconsMap, IconList, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, walkxcodeIcons, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
data := make(map[string][]string)
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
icons := make(IconsMap, len(data))
|
||||
iconList := make(IconList, 0, 2000)
|
||||
for fileType, files := range data {
|
||||
icons[fileType] = make(map[string]struct{}, len(files))
|
||||
for _, icon := range files {
|
||||
icons[fileType][icon] = struct{}{}
|
||||
iconList = append(iconList, "@walkxcode/"+icon)
|
||||
}
|
||||
}
|
||||
return icons, nil
|
||||
return icons, iconList, nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -211,7 +230,7 @@ format:
|
|||
"CreatedAt": "2024-08-16 00:27:23+00:00"
|
||||
}
|
||||
*/
|
||||
func fetchSelfhstIcons() (IconsMap, ReferenceDisplayNameMap, error) {
|
||||
func fetchSelfhstIcons() (IconsMap, IconList, ReferenceDisplayNameMap, error) {
|
||||
type SelfhStIcon struct {
|
||||
Name string `json:"Name"`
|
||||
Reference string `json:"Reference"`
|
||||
|
@ -225,25 +244,26 @@ func fetchSelfhstIcons() (IconsMap, ReferenceDisplayNameMap, error) {
|
|||
|
||||
req, err := http.NewRequest(http.MethodGet, selfhstIcons, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
data := make([]SelfhStIcon, 0, 2000)
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
iconList := make(IconList, 0, len(data)*3)
|
||||
icons := make(IconsMap)
|
||||
icons["svg"] = make(map[string]struct{}, len(data))
|
||||
icons["png"] = make(map[string]struct{}, len(data))
|
||||
|
@ -254,15 +274,18 @@ func fetchSelfhstIcons() (IconsMap, ReferenceDisplayNameMap, error) {
|
|||
for _, item := range data {
|
||||
if item.SVG == "Yes" {
|
||||
icons["svg"][item.Reference+".svg"] = struct{}{}
|
||||
iconList = append(iconList, "@selfhst/"+item.Reference+".svg")
|
||||
}
|
||||
if item.PNG == "Yes" {
|
||||
icons["png"][item.Reference+".png"] = struct{}{}
|
||||
iconList = append(iconList, "@selfhst/"+item.Reference+".png")
|
||||
}
|
||||
if item.WebP == "Yes" {
|
||||
icons["webp"][item.Reference+".webp"] = struct{}{}
|
||||
iconList = append(iconList, "@selfhst/"+item.Reference+".webp")
|
||||
}
|
||||
referenceToNames[item.Reference] = item.Name
|
||||
}
|
||||
|
||||
return icons, referenceToNames, nil
|
||||
return icons, iconList, referenceToNames, nil
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ func (r *HTTPRoute) addToLoadBalancer(parent task.Parent) {
|
|||
linked = l.(*HTTPRoute)
|
||||
lb = linked.loadBalancer
|
||||
lb.UpdateConfigIfNeeded(cfg)
|
||||
if linked.Raw.Homepage == nil && r.Raw.Homepage != nil {
|
||||
if linked.Raw.Homepage.IsEmpty() && !r.Raw.Homepage.IsEmpty() {
|
||||
linked.Raw.Homepage = r.Raw.Homepage
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -45,7 +45,7 @@ func HomepageCategories() []string {
|
|||
categories := make([]string, 0)
|
||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||
en := r.RawEntry()
|
||||
if en.Homepage == nil || en.Homepage.Category == "" {
|
||||
if en.Homepage.IsEmpty() || en.Homepage.Category == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := check[en.Homepage.Category]; ok {
|
||||
|
@ -63,13 +63,14 @@ func HomepageConfig(useDefaultCategories bool, categoryFilter, providerFilter st
|
|||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||
en := r.RawEntry()
|
||||
item := en.Homepage
|
||||
if item == nil {
|
||||
item = new(homepage.Item)
|
||||
item.Show = true
|
||||
|
||||
if item.IsEmpty() {
|
||||
item = homepage.NewItem(alias)
|
||||
}
|
||||
|
||||
if !item.IsEmpty() {
|
||||
item.Show = true
|
||||
if override := item.GetOverride(); override != item {
|
||||
hpCfg.Add(override)
|
||||
return
|
||||
}
|
||||
|
||||
if !item.Show {
|
||||
|
@ -85,8 +86,9 @@ func HomepageConfig(useDefaultCategories bool, categoryFilter, providerFilter st
|
|||
|
||||
if item.Name == "" {
|
||||
reference := r.TargetName()
|
||||
if r.RawEntry().Container != nil {
|
||||
reference = r.RawEntry().Container.ImageName
|
||||
cont := r.RawEntry().Container
|
||||
if cont != nil {
|
||||
reference = cont.ImageName
|
||||
}
|
||||
name, ok := internal.GetDisplayName(reference)
|
||||
if ok {
|
||||
|
@ -138,7 +140,7 @@ func HomepageConfig(useDefaultCategories bool, categoryFilter, providerFilter st
|
|||
}
|
||||
|
||||
item.AltURL = r.TargetURL().String()
|
||||
hpCfg.Add(item.GetOverriddenItem())
|
||||
hpCfg.Add(item)
|
||||
})
|
||||
return hpCfg
|
||||
}
|
||||
|
|
|
@ -172,6 +172,10 @@ func (e *RawEntry) Finalize() {
|
|||
}
|
||||
}
|
||||
|
||||
if e.Homepage == nil {
|
||||
e.Homepage = homepage.NewItem(e.Alias)
|
||||
}
|
||||
|
||||
e.finalized = true
|
||||
}
|
||||
|
||||
|
|
|
@ -18,3 +18,11 @@ func Intersect[T comparable, Slice ~[]T](slice1 Slice, slice2 Slice) Slice {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// Slice returns a slice of the first n elements in slice like javascript's slice.
|
||||
func Slice[T any](slice []T, n int) []T {
|
||||
if n >= len(slice) {
|
||||
return slice
|
||||
}
|
||||
return slice[:n]
|
||||
}
|
||||
|
|
|
@ -113,6 +113,10 @@ func (h *DirWatcher) start() {
|
|||
relPath := strings.TrimPrefix(fsEvent.Name, h.dir)
|
||||
relPath = strings.TrimPrefix(relPath, "/")
|
||||
|
||||
if len(relPath) > 0 && relPath[0] == '.' { // hideden file
|
||||
continue
|
||||
}
|
||||
|
||||
msg := Event{
|
||||
Type: events.EventTypeFile,
|
||||
ActorName: relPath,
|
||||
|
|
Loading…
Add table
Reference in a new issue