Merge branch 'main' into dev

This commit is contained in:
yusing
2026-01-01 18:25:56 +08:00
7 changed files with 80 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strings"
"time" "time"
"github.com/yusing/godoxy/agent/pkg/agent" "github.com/yusing/godoxy/agent/pkg/agent"
@@ -43,10 +44,22 @@ func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
r.URL.Scheme = "" // Strip the {API_BASE}/proxy/http prefix while preserving URL escaping.
r.URL.Host = "" //
r.URL.Path = r.URL.Path[agent.HTTPProxyURLPrefixLen:] // strip the {API_BASE}/proxy/http prefix // NOTE: `r.URL.Path` is decoded. If we rewrite it without keeping `RawPath`
r.RequestURI = r.URL.String() // in sync, Go may re-escape the path (e.g. turning "%5B" into "%255B"),
// which breaks urls with percent-encoded characters, like Next.js static chunk URLs.
prefix := agent.APIEndpointBase + agent.EndpointProxyHTTP
r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix)
if r.URL.RawPath != "" {
if after, ok := strings.CutPrefix(r.URL.RawPath, prefix); ok {
r.URL.RawPath = after
} else {
// RawPath is no longer a valid encoding for Path; force Go to re-derive it.
r.URL.RawPath = ""
}
}
r.RequestURI = ""
rp := &httputil.ReverseProxy{ rp := &httputil.ReverseProxy{
Director: func(r *http.Request) { Director: func(r *http.Request) {

View File

@@ -4555,6 +4555,11 @@
"x-nullable": false, "x-nullable": false,
"x-omitempty": false "x-omitempty": false
}, },
"is_excluded": {
"type": "boolean",
"x-nullable": false,
"x-omitempty": false
},
"statuses": { "statuses": {
"type": "array", "type": "array",
"items": { "items": {

View File

@@ -1056,6 +1056,8 @@ definitions:
type: number type: number
is_docker: is_docker:
type: boolean type: boolean
is_excluded:
type: boolean
statuses: statuses:
items: items:
$ref: '#/definitions/RouteStatus' $ref: '#/definitions/RouteStatus'

View File

@@ -146,11 +146,18 @@ func findRouteAnyDomain(host string) types.HTTPRoute {
if r, ok := routes.HTTP.Get(host); ok { if r, ok := routes.HTTP.Get(host); ok {
return r return r
} }
// try striping the trailing :port from the host
if before, _, ok := strings.Cut(host, ":"); ok {
if r, ok := routes.HTTP.Get(before); ok {
return r
}
}
return nil return nil
} }
func findRouteByDomains(domains []string) func(host string) types.HTTPRoute { func findRouteByDomains(domains []string) func(host string) types.HTTPRoute {
return func(host string) types.HTTPRoute { return func(host string) types.HTTPRoute {
host, _, _ = strings.Cut(host, ":") // strip the trailing :port
for _, domain := range domains { for _, domain := range domains {
if target, ok := strings.CutSuffix(host, domain); ok { if target, ok := strings.CutSuffix(host, domain); ok {
if r, ok := routes.HTTP.Get(target); ok { if r, ok := routes.HTTP.Get(target); ok {

View File

@@ -128,3 +128,47 @@ func TestFindRouteByDomainsExactMatch(t *testing.T) {
run(t, tests, testsNoMatch) run(t, tests, testsNoMatch)
} }
func TestFindRouteWithPort(t *testing.T) {
t.Run("AnyDomain", func(t *testing.T) {
addRoute("app1")
addRoute("app2.com")
tests := []string{
"app1:8080",
"app1.domain.com:8080",
"app2.com:8080",
}
testsNoMatch := []string{
"app11",
"app2.co",
"app2.co:8080",
}
run(t, tests, testsNoMatch)
})
t.Run("ByDomains", func(t *testing.T) {
ep.SetFindRouteDomains([]string{
".domain.com",
})
addRoute("app1")
addRoute("app2")
addRoute("app3.domain.com")
tests := []string{
"app1.domain.com:8080",
"app2:8080", // exact match fallback
"app3.domain.com:8080",
}
testsNoMatch := []string{
"app11",
"app1.domain.co",
"app1.domain.co:8080",
"app2.co",
"app2.co:8080",
"app3.domain.co",
"app3.domain.co:8080",
}
run(t, tests, testsNoMatch)
})
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/yusing/ds/ordered" "github.com/yusing/ds/ordered"
"github.com/yusing/godoxy/internal/homepage/widgets" "github.com/yusing/godoxy/internal/homepage/widgets"
"github.com/yusing/godoxy/internal/serialization" "github.com/yusing/godoxy/internal/serialization"
strutils "github.com/yusing/goutils/strings"
) )
type ( type (
@@ -146,13 +147,13 @@ func (c *Category) sortByClicks() {
return 1 return 1
} }
// fallback to alphabetical // fallback to alphabetical
return strings.Compare(a.Name, b.Name) return strings.Compare(strutils.Title(a.Name), strutils.Title(b.Name))
}) })
} }
func (c *Category) sortByAlphabetical() { func (c *Category) sortByAlphabetical() {
slices.SortStableFunc(c.Items, func(a, b *Item) int { slices.SortStableFunc(c.Items, func(a, b *Item) int {
return strings.Compare(a.Name, b.Name) return strings.Compare(strutils.Title(a.Name), strutils.Title(b.Name))
}) })
} }

View File

@@ -33,6 +33,7 @@ type (
Idle float32 `json:"idle"` Idle float32 `json:"idle"`
AvgLatency float32 `json:"avg_latency"` AvgLatency float32 `json:"avg_latency"`
IsDocker bool `json:"is_docker"` IsDocker bool `json:"is_docker"`
IsExcluded bool `json:"is_excluded"`
CurrentStatus types.HealthStatus `json:"current_status" swaggertype:"string" enums:"healthy,unhealthy,unknown,napping,starting"` CurrentStatus types.HealthStatus `json:"current_status" swaggertype:"string" enums:"healthy,unhealthy,unknown,napping,starting"`
Statuses []Status `json:"statuses"` Statuses []Status `json:"statuses"`
} // @name RouteUptimeAggregate } // @name RouteUptimeAggregate
@@ -156,6 +157,7 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated {
CurrentStatus: status, CurrentStatus: status,
Statuses: statuses, Statuses: statuses,
IsDocker: r != nil && r.IsDocker(), IsDocker: r != nil && r.IsDocker(),
IsExcluded: r == nil || r.ShouldExclude(),
} }
} }
return result return result