feat: support selfh.st icons, support homepage config overriding

This commit is contained in:
yusing
2025-01-20 17:42:17 +08:00
parent 68771ce399
commit 64e85c3076
13 changed files with 591 additions and 44 deletions

View File

@@ -34,6 +34,7 @@ func NewHandler(cfg config.ConfigInstance) http.Handler {
mux.HandleFunc("GET", "/v1/health/ws", auth.RequireAuth(useCfg(cfg, v1.HealthWS)))
mux.HandleFunc("GET", "/v1/logs/ws", auth.RequireAuth(useCfg(cfg, v1.LogsWS())))
mux.HandleFunc("GET", "/v1/favicon/{alias}", auth.RequireAuth(favicon.GetFavIcon))
mux.HandleFunc("POST", "/v1/homepage/set", auth.RequireAuth(v1.SetHomePageOverrides))
defaultAuth := auth.GetDefaultAuth()
if defaultAuth != nil {

View File

@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
@@ -88,6 +89,8 @@ func GetFavIcon(w http.ResponseWriter, req *http.Request) {
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)
}
} else {
// try extract from "link[rel=icon]"
@@ -117,8 +120,8 @@ func ResetIconCache(route route.HTTPRoute) {
func loadIconCache(key string) (icon []byte, ok bool) {
iconCacheMu.RLock()
defer iconCacheMu.RUnlock()
icon, ok = iconCache[key]
iconCacheMu.RUnlock()
return
}
@@ -169,9 +172,9 @@ func sanitizeName(name string) string {
return strings.ToLower(nameSanitizer.Replace(name))
}
func fetchWalkxcodeIcon(filetype string, name string) ([]byte, int, string) {
func fetchWalkxcodeIcon(filetype, name string) ([]byte, int, string) {
// if icon isn't in the list, no need to fetch
if !internal.HasIcon(name, filetype) {
if !internal.HasWalkxCodeIcon(name, filetype) {
logging.Debug().
Str("filetype", filetype).
Str("name", name).
@@ -184,10 +187,39 @@ func fetchWalkxcodeIcon(filetype string, name string) ([]byte, int, string) {
return icon, http.StatusOK, ""
}
url := homepage.DashboardIconBaseURL + filetype + "/" + name + "." + filetype
// 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)
}
func fetchIcon(filetype, filename string) (icon []byte, status int, errMsg string) {
icon, status, errMsg = fetchSelfhStIcon(filetype, filename)
if icon != nil {
return
}
icon, status, errMsg = fetchWalkxcodeIcon(filetype, filename)
return
}
func findIcon(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, status int, errMsg string) {
key := r.TargetName()
icon, ok := loadIconCache(key)
@@ -198,10 +230,10 @@ func findIcon(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, st
return icon, http.StatusOK, ""
}
icon, status, errMsg = fetchWalkxcodeIcon("png", sanitizeName(r.TargetName()))
icon, status, errMsg = fetchIcon("png", sanitizeName(r.TargetName()))
cont := r.RawEntry().Container
if icon == nil && cont != nil {
icon, status, errMsg = fetchWalkxcodeIcon("png", sanitizeName(cont.ImageName))
icon, status, errMsg = fetchIcon("png", sanitizeName(cont.ImageName))
}
if icon == nil {
// fallback to parse html
@@ -213,7 +245,6 @@ func findIcon(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, st
}
func findIconSlow(r route.HTTPRoute, req *http.Request, uri string) (icon []byte, status int, errMsg string) {
c := newContent()
ctx, cancel := context.WithTimeoutCause(req.Context(), 3*time.Second, errors.New("favicon request timeout"))
defer cancel()
newReq := req.WithContext(ctx)
@@ -233,6 +264,8 @@ func findIconSlow(r route.HTTPRoute, req *http.Request, uri string) (icon []byte
newReq.URL.RawPath = u.RawPath
newReq.URL.RawQuery = u.RawQuery
newReq.RequestURI = u.String()
c := newContent()
r.ServeHTTP(c, newReq)
if c.status != http.StatusOK {
switch c.status {

View File

@@ -0,0 +1,58 @@
package v1
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"
)
const (
HomepageOverrideDisplayname = "display_name"
HomepageOverrideDisplayOrder = "display_order"
HomepageOverrideDisplayCategory = "display_category"
HomepageOverrideCategoryOrder = "category_order"
HomepageOverrideCategoryName = "category_name"
HomepageOverrideIcon = "icon"
HomepageOverrideShow = "show"
)
func SetHomePageOverrides(w http.ResponseWriter, r *http.Request) {
what, which, value := r.FormValue("what"), r.FormValue("which"), r.FormValue("value")
if what == "" || which == "" {
http.Error(w, "missing what or which", http.StatusBadRequest)
return
}
if value == "" {
http.Error(w, "missing value", http.StatusBadRequest)
return
}
overrides := homepage.GetJSONConfig()
switch what {
case HomepageOverrideDisplayname:
utils.RespondError(w, overrides.SetDisplayNameOverride(which, value))
case HomepageOverrideDisplayCategory:
utils.RespondError(w, overrides.SetDisplayCategoryOverride(which, value))
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:
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))
}
default:
http.Error(w, "invalid what", http.StatusBadRequest)
}
}

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"strings"
"github.com/yusing/go-proxy/internal"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
"github.com/yusing/go-proxy/internal/common"
config "github.com/yusing/go-proxy/internal/config/types"
@@ -24,6 +25,7 @@ const (
ListHomepageConfig = "homepage_config"
ListRouteProviders = "route_providers"
ListHomepageCategories = "homepage_categories"
ListIcons = "icons"
ListTasks = "tasks"
)
@@ -58,6 +60,13 @@ func List(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
U.RespondJSON(w, r, cfg.RouteProviderList())
case ListHomepageCategories:
U.RespondJSON(w, r, routequery.HomepageCategories())
case ListIcons:
icons, err := internal.ListAvailableIcons()
if err != nil {
U.RespondError(w, err)
return
}
U.RespondJSON(w, r, icons)
case ListTasks:
U.RespondJSON(w, r, task.DebugTaskList())
default:
@@ -80,7 +89,7 @@ func listRoute(which string) any {
}
func listFiles(w http.ResponseWriter, r *http.Request) {
files, err := utils.ListFiles(common.ConfigBasePath, 0)
files, err := utils.ListFiles(common.ConfigBasePath, 0, true)
if err != nil {
U.HandleErr(w, r, err)
return
@@ -97,7 +106,7 @@ func listFiles(w http.ResponseWriter, r *http.Request) {
resp[t] = append(resp[t], file)
}
mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0)
mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true)
if err != nil {
U.HandleErr(w, r, err)
return