feat: M3 load balancing + health check (model/render/admin API status/UI)

This commit is contained in:
2026-08-17 17:45:03 +08:00
parent bd3a62a017
commit 467c7124ab
10 changed files with 658 additions and 33 deletions

View File

@ -6,6 +6,8 @@ import (
"net/http"
"net/http/httptest"
"path/filepath"
"strconv"
"strings"
"testing"
"webui4frpc/internal/process"
@ -138,3 +140,55 @@ func TestSettingsPutRoundTrip(t *testing.T) {
t.Fatalf("settings after reload = %+v", got)
}
}
// TestFetchProxyStatesFromAdminAPI verifies the M3 status path: we mock the
// frpc admin API (/api/status with Basic Auth) and confirm fetchProxyStates
// parses per-proxy rows and connStateAdmin derives the right summary.
func TestFetchProxyStatesFromAdminAPI(t *testing.T) {
// Mock frpc admin server.
admin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u, p, ok := r.BasicAuth()
if !ok || u != "admin" || p != "pw" {
w.WriteHeader(http.StatusUnauthorized)
return
}
if r.URL.Path != "/api/status" {
http.NotFound(w, r)
return
}
_ = json.NewEncoder(w).Encode(map[string][]proxyState{
"tcp": {
{Name: "web1", Type: "tcp", Status: "running", LocalAddr: "127.0.0.1:8080", RemoteAddr: "1.2.3.4:8080"},
{Name: "web2", Type: "tcp", Status: "check failed", Err: "health check failed"},
},
})
}))
defer admin.Close()
u := strings.TrimPrefix(admin.URL, "http://")
host, portStr, _ := strings.Cut(u, ":")
port, _ := strconv.Atoi(portStr)
states, err := fetchProxyStates(host, port, "admin", "pw")
if err != nil {
t.Fatalf("fetchProxyStates: %v", err)
}
if len(states) != 2 {
t.Fatalf("states = %+v", states)
}
if states[0].Status != "running" || states[1].Status != "check failed" {
t.Fatalf("unexpected states = %+v", states)
}
// connStateAdmin: one running -> connected.
if got := connStateAdmin(states); got != "connected" {
t.Fatalf("connStateAdmin = %q, want connected", got)
}
// All failed -> failed.
if got := connStateAdmin([]proxyState{{Name: "a", Status: "check failed"}, {Name: "b", Status: "start error"}}); got != "failed" {
t.Fatalf("connStateAdmin(all failed) = %q, want failed", got)
}
// wait start -> connecting.
if got := connStateAdmin([]proxyState{{Name: "a", Status: "wait start"}}); got != "connecting" {
t.Fatalf("connStateAdmin(wait start) = %q, want connecting", got)
}
}