feat(nodes): per-node client IP attribution for IP-limit

Record each panel's own Xray IP observations under its panelGuid and merge each node's guid-keyed report on the master, so the panel can tell which node a client IP is connecting through (the flat inbound_client_ips union is pushed back to every node and cannot attribute). Adds the NodeClientIp model + migration, the clientIpsByGuid endpoint and node-sync merge, node-name labels in the client IP log, and cleanup on node deletion.
This commit is contained in:
MHSanaei
2026-06-15 23:50:05 +02:00
parent d882d6aa74
commit 9385b6c609
13 changed files with 598 additions and 45 deletions
+18
View File
@@ -617,3 +617,21 @@ func (r *Remote) PushAllClientIps(ctx context.Context, ips []model.InboundClient
_, err := r.do(ctx, http.MethodPost, "panel/api/server/clientIps", ips)
return err
}
// FetchClientIpsByGuid pulls the node's per-node IP attribution subtree
// (guid -> email -> observed IPs). Unlike FetchAllClientIps (the flat union the
// master also pushes back), this preserves which physical node each IP is on.
// Returns an empty map for older nodes that lack the endpoint.
func (r *Remote) FetchClientIpsByGuid(ctx context.Context) (map[string]map[string][]model.ClientIpEntry, error) {
env, err := r.do(ctx, http.MethodPost, "panel/api/clients/clientIpsByGuid", nil)
if err != nil {
return nil, err
}
out := map[string]map[string][]model.ClientIpEntry{}
if len(env.Obj) > 0 {
if err := json.Unmarshal(env.Obj, &out); err != nil {
return nil, fmt.Errorf("decode client ips by guid: %w", err)
}
}
return out, nil
}