using System.Text.Json.Serialization; namespace NATS.Server.Monitoring; /// /// Connection information response. Corresponds to Go server/monitor.go Connz struct. /// public sealed class Connz { [JsonPropertyName("server_id")] public string Id { get; set; } = ""; [JsonPropertyName("now")] public DateTime Now { get; set; } [JsonPropertyName("num_connections")] public int NumConns { get; set; } [JsonPropertyName("total")] public int Total { get; set; } [JsonPropertyName("offset")] public int Offset { get; set; } [JsonPropertyName("limit")] public int Limit { get; set; } [JsonPropertyName("connections")] public ConnInfo[] Conns { get; set; } = []; } /// /// Detailed information on a per-connection basis. /// Corresponds to Go server/monitor.go ConnInfo struct. /// public sealed class ConnInfo { [JsonPropertyName("cid")] public ulong Cid { get; set; } [JsonPropertyName("kind")] public string Kind { get; set; } = ""; [JsonPropertyName("type")] public string Type { get; set; } = ""; [JsonPropertyName("ip")] public string Ip { get; set; } = ""; [JsonPropertyName("port")] public int Port { get; set; } [JsonPropertyName("start")] public DateTime Start { get; set; } [JsonPropertyName("last_activity")] public DateTime LastActivity { get; set; } [JsonPropertyName("stop")] public DateTime? Stop { get; set; } [JsonPropertyName("reason")] public string Reason { get; set; } = ""; [JsonPropertyName("rtt")] public string Rtt { get; set; } = ""; [JsonPropertyName("uptime")] public string Uptime { get; set; } = ""; [JsonPropertyName("idle")] public string Idle { get; set; } = ""; [JsonPropertyName("pending_bytes")] public int Pending { get; set; } [JsonPropertyName("in_msgs")] public long InMsgs { get; set; } [JsonPropertyName("out_msgs")] public long OutMsgs { get; set; } [JsonPropertyName("in_bytes")] public long InBytes { get; set; } [JsonPropertyName("out_bytes")] public long OutBytes { get; set; } [JsonPropertyName("subscriptions")] public uint NumSubs { get; set; } [JsonPropertyName("subscriptions_list")] public string[] Subs { get; set; } = []; [JsonPropertyName("subscriptions_list_detail")] public SubDetail[] SubsDetail { get; set; } = []; [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("lang")] public string Lang { get; set; } = ""; [JsonPropertyName("version")] public string Version { get; set; } = ""; [JsonPropertyName("authorized_user")] public string AuthorizedUser { get; set; } = ""; [JsonPropertyName("account")] public string Account { get; set; } = ""; [JsonPropertyName("tls_version")] public string TlsVersion { get; set; } = ""; [JsonPropertyName("tls_cipher_suite")] public string TlsCipherSuite { get; set; } = ""; [JsonPropertyName("tls_peer_cert_subject")] public string TlsPeerCertSubject { get; set; } = ""; [JsonPropertyName("tls_first")] public bool TlsFirst { get; set; } [JsonPropertyName("mqtt_client")] public string MqttClient { get; set; } = ""; [JsonPropertyName("jwt_issuer_key")] public string JwtIssuerKey { get; set; } = ""; [JsonPropertyName("jwt_tags")] public string JwtTags { get; set; } = ""; [JsonPropertyName("proxy")] public string Proxy { get; set; } = ""; } /// /// Subscription detail information. /// Corresponds to Go server/monitor.go SubDetail struct. /// public sealed class SubDetail { [JsonPropertyName("account")] public string Account { get; set; } = ""; [JsonPropertyName("subject")] public string Subject { get; set; } = ""; [JsonPropertyName("qgroup")] public string Queue { get; set; } = ""; [JsonPropertyName("sid")] public string Sid { get; set; } = ""; [JsonPropertyName("msgs")] public long Msgs { get; set; } [JsonPropertyName("max")] public long Max { get; set; } [JsonPropertyName("cid")] public ulong Cid { get; set; } } /// /// Lightweight connection info record used by the account-scoped filter API. /// Decoupled from so filter logic can be tested without a running server. /// Go reference: monitor.go ConnInfo (subset of fields relevant to account filtering). /// public sealed record ConnzConnectionInfo( ulong ClientId, string RemoteAddress, string? AccountName, string? Name, DateTime ConnectedAt, long InMsgs, long OutMsgs, long InBytes, long OutBytes); /// /// Paginated result returned by . /// public sealed record ConnzFilterResult( IReadOnlyList Connections, int Total, int Offset, int Limit); /// /// Query-string options for the account-scoped filter API. /// Parses the ?acc=, ?state=, ?offset=, and ?limit= parameters that the Go server /// accepts on the /connz endpoint. /// Go reference: monitor.go ConnzOptions / Connz(). /// public sealed class ConnzFilterOptions { public string? AccountFilter { get; init; } /// "open", "closed", or "any" (default: "open") public string? StateFilter { get; init; } public int Offset { get; init; } public int Limit { get; init; } = 1024; /// /// Parses a raw query string (e.g. "?acc=ACCOUNT&state=open&offset=0&limit=100") /// into a instance. /// public static ConnzFilterOptions Parse(string? queryString) { if (string.IsNullOrEmpty(queryString)) return new ConnzFilterOptions(); // Strip leading '?' var qs = queryString.TrimStart('?'); string? accountFilter = null; string? stateFilter = null; int offset = 0; int limit = 1024; foreach (var pair in qs.Split('&', StringSplitOptions.RemoveEmptyEntries)) { var eqIdx = pair.IndexOf('='); if (eqIdx < 0) continue; var key = Uri.UnescapeDataString(pair[..eqIdx]).ToLowerInvariant(); var value = Uri.UnescapeDataString(pair[(eqIdx + 1)..]); switch (key) { case "acc": accountFilter = value; break; case "state": stateFilter = value.ToLowerInvariant(); break; case "offset" when int.TryParse(value, out var o): offset = o; break; case "limit" when int.TryParse(value, out var l): limit = l; break; } } return new ConnzFilterOptions { AccountFilter = accountFilter, StateFilter = stateFilter, Offset = offset, Limit = limit, }; } } /// /// Pure filtering helper for collections. /// Provides account-scoped filtering and pagination that mirror the Go server's /// /connz ?acc= behavior. /// Go reference: monitor.go Connz() — account filter branch. /// public static class ConnzFilter { /// /// Filters to only those whose /// matches /// using a case-insensitive ordinal comparison. /// public static IReadOnlyList FilterByAccount( IEnumerable connections, string accountName) { return connections .Where(c => string.Equals(c.AccountName, accountName, StringComparison.OrdinalIgnoreCase)) .ToList(); } /// /// Applies all filters specified in and returns a paginated result. /// public static ConnzFilterResult ApplyFilters( IEnumerable connections, ConnzFilterOptions options) { IEnumerable filtered = connections; if (!string.IsNullOrEmpty(options.AccountFilter)) filtered = filtered.Where(c => string.Equals(c.AccountName, options.AccountFilter, StringComparison.OrdinalIgnoreCase)); var list = filtered.ToList(); var total = list.Count; var paged = list.Skip(options.Offset).Take(options.Limit).ToList(); return new ConnzFilterResult(paged, total, options.Offset, options.Limit); } } /// /// Sort options for connection listing. /// Corresponds to Go server/monitor_sort_opts.go SortOpt type. /// public enum SortOpt { ByCid, ByStart, BySubs, ByPending, ByMsgsTo, ByMsgsFrom, ByBytesTo, ByBytesFrom, ByLast, ByIdle, ByUptime, ByRtt, ByStop, ByReason, } /// /// Connection state filter. /// Corresponds to Go server/monitor.go ConnState type. /// public enum ConnState { Open, Closed, All, } /// /// Options passed to Connz() for filtering and sorting. /// Corresponds to Go server/monitor.go ConnzOptions struct. /// public sealed class ConnzOptions { public SortOpt Sort { get; set; } = SortOpt.ByCid; public bool Subscriptions { get; set; } public bool SubscriptionsDetail { get; set; } public ConnState State { get; set; } = ConnState.Open; public string User { get; set; } = ""; public string Account { get; set; } = ""; public string FilterSubject { get; set; } = ""; public string MqttClient { get; set; } = ""; /// /// When non-zero, returns only the connection with this CID. /// Go reference: monitor.go ConnzOptions.CID. /// public ulong Cid { get; set; } /// /// Whether to include authorized user info. /// Go reference: monitor.go ConnzOptions.Username. /// public bool Auth { get; set; } public int Offset { get; set; } public int Limit { get; set; } = 1024; }