mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-21 17:37:56 +00:00
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:
@ -233,8 +233,27 @@ func fetchFromURL(ctx context.Context, version string) ([]byte, error) {
|
||||
|
||||
// EnsureVersion makes sure a given frpc version is cached locally. It follows
|
||||
// the distribution priority: local cache -> cluster peers -> external URL.
|
||||
// pathSafeVersion normalizes a requested version and rejects anything that
|
||||
// could escape the cache dir (e.g. "../../x").
|
||||
func pathSafeVersion(version string) (string, bool) {
|
||||
v := strings.TrimPrefix(version, "v")
|
||||
if v == "" || strings.ContainsAny(v, "/\\") || strings.Contains(v, "..") {
|
||||
return "", false
|
||||
}
|
||||
for _, c := range v {
|
||||
if (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '.' && c != '-' && c != '_' {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
func (r *Registry) EnsureVersion(ctx context.Context, version string) (string, error) {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
safe, ok := pathSafeVersion(version)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid version %q", version)
|
||||
}
|
||||
version = safe
|
||||
if r.HasVersion(version) {
|
||||
return r.BinaryPath(version), nil
|
||||
}
|
||||
@ -403,3 +422,47 @@ func (r *Registry) SyncFromPeers(ctx context.Context) error {
|
||||
func (r *Registry) SelfInfo() NodeInfo {
|
||||
return NodeInfo{Addr: "self", Version: "", Cache: r.CachedVersions()}
|
||||
}
|
||||
|
||||
// CacheEntry describes one locally cached frpc version (for UI + prune).
|
||||
type CacheEntry struct {
|
||||
Version string `json:"version"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
ModTime int64 `json:"modTime"`
|
||||
}
|
||||
|
||||
// CacheInfo lists all locally cached frpc versions with metadata.
|
||||
func (r *Registry) CacheInfo() []CacheEntry {
|
||||
entries, err := os.ReadDir(r.BinDir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var out []CacheEntry
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() || !strings.HasPrefix(e.Name(), "frpc-") {
|
||||
continue
|
||||
}
|
||||
ver := strings.TrimPrefix(e.Name(), "frpc-")
|
||||
bin := filepath.Join(r.BinDir, e.Name(), "frpc")
|
||||
info, err := os.Stat(bin)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, CacheEntry{Version: ver, Path: bin, Size: info.Size(), ModTime: info.ModTime().Unix()})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// PruneCache removes all cached versions except the newest keep (LRU-ish).
|
||||
func (r *Registry) PruneCache(keep int) []string {
|
||||
entries := r.CacheInfo()
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].ModTime > entries[j].ModTime })
|
||||
var removed []string
|
||||
for i := keep; i < len(entries); i++ {
|
||||
dir := filepath.Join(r.BinDir, "frpc-"+entries[i].Version)
|
||||
if err := os.RemoveAll(dir); err == nil {
|
||||
removed = append(removed, entries[i].Version)
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user