feat: M3 cluster page (LB groups + health check status) e2e-verified failover; M6 cache API + version path hardening + frontend cluster methods

This commit is contained in:
2026-08-17 22:44:04 +08:00
parent a842198447
commit 5bd70b90c0
11 changed files with 470 additions and 128 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webui-frpc</title>
<script type="module" crossorigin src="/assets/index-C04K5jB8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B6NLA8yp.css">
<script type="module" crossorigin src="/assets/index-C9C9_j3w.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C5T3LFGh.css">
</head>
<body>
<div id="app"></div>

View File

@ -89,6 +89,7 @@ func NewServeMux(h *Handler) (http.Handler, error) {
// M6: cluster nodes + per-node cached versions (UI + discovery).
mux.HandleFunc(apiPrefix+"/cluster/nodes", auth(h.handleClusterNodes))
mux.HandleFunc(apiPrefix+"/cluster/cache", auth(h.handleClusterCache))
// M6: peer-to-peer binary exchange endpoint (Basic Auth, same creds).
// Not under /api so peers hit it directly; auth still applied.
@ -320,6 +321,34 @@ func (h *Handler) handleFrpcBinary(w http.ResponseWriter, r *http.Request) {
_, _ = io.Copy(w, f)
}
// handleClusterCache lists cached frpc versions (GET) or prunes (POST {keep:N}).
func (h *Handler) handleClusterCache(w http.ResponseWriter, r *http.Request) {
if h.Cluster == nil {
http.Error(w, "cluster registry not enabled", http.StatusNotFound)
return
}
switch r.Method {
case http.MethodGet:
writeJSON(w, http.StatusOK, map[string]any{"cache": h.Cluster.CacheInfo()})
case http.MethodPost:
var req struct {
Keep int `json:"keep"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "parse json: "+err.Error(), http.StatusBadRequest)
return
}
if req.Keep < 0 || req.Keep > 50 {
http.Error(w, "keep in [0,50]", http.StatusBadRequest)
return
}
removed := h.Cluster.PruneCache(req.Keep)
writeJSON(w, http.StatusOK, map[string]any{"removed": removed, "cache": h.Cluster.CacheInfo()})
default:
methodNotAllowed(w)
}
}
// frpcVersionOf extracts the frpc version from a binary path like
// .../bin/frpc-0.71.0/frpc. Empty when not a versioned cache path.
func frpcVersionOf(binPath string) string {