Repository: nats-io/nats-server

Issue:
Leafnode CID/LID not shown in connection list
### Observed behavior

`nats server request connections` and `/connz`  as of recently (2.11+?) do not show the leafnodes connections anymore.

### Expected behavior

Show leafnode connections with "kind=leafnode" (or similar)

### Server and client version

2.11.3 and later - All operating systems (Windows, Linux, Mac verified)

### Host environment

_No response_

### Steps to reproduce

_No response_

Discussion:
@roeschter: Note: leafnodes are only reported in under connections when the account is specified:  
`nats server request connections --filter-account <account>`

This is unexpected - when listing connections from the system account without filter all connections should be reported

See:  https://github.com/nats-io/nats-server/blob/main/server/monitor.go#L265-L279

Solution patch:
diff --git a/server/monitor.go b/server/monitor.go
index c6b16b21..cf0bbecf 100644
--- a/server/monitor.go
+++ b/server/monitor.go
@@ -23,6 +23,7 @@ import (
 	"encoding/json"
 	"expvar"
 	"fmt"
+	"maps"
 	"math"
 	"net"
 	"net/http"
@@ -282,7 +283,9 @@ func (s *Server) Connz(opts *ConnzOptions) (*Connz, error) {
 	s.mu.RLock()
 	// Default to all client unless filled in above.
 	if clist == nil {
-		clist = s.clients
+		clist = make(map[uint64]*client, len(s.clients)+len(s.leafs))
+		maps.Copy(clist, s.clients)
+		maps.Copy(clist, s.leafs)
 	}
 
 	// copy the server id for monitoring



Test patch:
diff --git a/server/monitor_test.go b/server/monitor_test.go
index f3500f3f..88aa2016 100644
--- a/server/monitor_test.go
+++ b/server/monitor_test.go
@@ -27,6 +27,7 @@ import (
 	"path/filepath"
 	"reflect"
 	"runtime"
+	"slices"
 	"sort"
 	"strings"
 	"sync"
@@ -5700,8 +5701,11 @@ func TestMonitorConnzOperatorAccountNames(t *testing.T) {
 	for pollMode := 0; pollMode < 2; pollMode++ {
 		url := fmt.Sprintf("http://127.0.0.1:%d/connz?auth=1", s.MonitorAddr().Port)
 		connz := pollConnz(t, s, pollMode, url, &ConnzOptions{Username: true})
-		require_Equal(t, connz.NumConns, 1)
-		ci := connz.Conns[0]
+		require_Equal(t, connz.NumConns, 2)
+		idx := slices.IndexFunc(connz.Conns, func(c *ConnInfo) bool {
+			return c.Kind == kindStringMap[CLIENT]
+		})
+		ci := connz.Conns[idx]
 		require_Equal(t, ci.Account, accPub)
 		require_Equal(t, ci.NameTag, accName)
 	}
@@ -5822,6 +5826,88 @@ func TestMonitorConnzSortByRTT(t *testing.T) {
 	}
 }
 
+func TestMonitorConnzIncludesLeafnodes(t *testing.T) {
+	content := `
+		server_name: "hub"
+		listen: "127.0.0.1:-1"
+		http: "127.0.0.1:-1"
+		operator = "../test/configs/nkeys/op.jwt"
+		resolver = MEMORY
+		ping_interval = 1
+		leafnodes {
+			listen: "127.0.0.1:-1"
+		}
+	`
+	conf := createConfFile(t, []byte(content))
+	sb, ob := RunServerWithConfig(conf)
+	defer sb.Shutdown()
+
+	createAcc := func(t *testing.T) (*Account, string) {
+		t.Helper()
+		acc, akp := createAccount(sb)
+		kp, _ := nkeys.CreateUser()
+		pub, _ := kp.PublicKey()
+		nuc := jwt.NewUserClaims(pub)
+		ujwt, err := nuc.Encode(akp)
+		if err != nil {
+			t.Fatalf("Error generating user JWT: %v", err)
+		}
+		seed, _ := kp.Seed()
+		creds := genCredsFile(t, ujwt, seed)
+		return acc, creds
+	}
+	acc, mycreds := createAcc(t)
+	leafName := "my-leaf-node"
+
+	content = `
+		port: -1
+		http: "127.0.0.1:-1"
+		ping_interval = 1
+		server_name: %s
+		accounts {
+			%s {
+				users [
+					{user: user1, password: pwd}
+				]
+			}
+		}
+		leafnodes {
+			remotes = [
+				{
+					account: "%s"
+					url: nats-leaf://127.0.0.1:%d
+					credentials: '%s'
+				}
+			]
+		}
+		`
+	config := fmt.Sprintf(content,
+		leafName,
+		acc.Name,
+		acc.Name, ob.LeafNode.Port, mycreds)
+	conf = createConfFile(t, []byte(config))
+	sa, _ := RunServerWithConfig(conf)
+	defer sa.Shutdown()
+
+	checkFor(t, time.Second, 15*time.Millisecond, func() error {
+		if n := sa.NumLeafNodes(); n != 1 {
+			return fmt.Errorf("Expected 1 leaf connection, got %v", n)
+		}
+		return nil
+	})
+
+	for test, options := range map[string]*ConnzOptions{
+		"WithoutAccount": {},
+		"WithAccount":    {Account: acc.Name},
+	} {
+		t.Run(test, func(t *testing.T) {
+			c := pollConnz(t, sb, 1, "http://127.0.0.1:%d/connz", options)
+			require_Equal(t, c.NumConns, 1)
+			require_Equal(t, c.Conns[0].Kind, kindStringMap[LEAF])
+		})
+	}
+}
+
 // https://github.com/nats-io/nats-server/issues/4144
 func TestMonitorAccountszMappingOrderReporting(t *testing.T) {
 	conf := createConfFile(t, []byte(`