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;
}